您好,欢迎访问三七文档
课程内容性能瓶颈代码层面Jvm层面编码异常结构io流回收参数家里厨房的水池下水堵了,我们要找原因,首先得知道水池的下水道都有哪些部分:简单的看,可以把下水道分解为水漏、上连接管、回水弯、下连接管最后接入地漏。再查找堵塞位置时,我们就可以将水直接导入回水弯,排除水漏和上连接管道堵塞的可能常见性能瓶颈的识别•常见性能瓶颈的识别1.CPU占有率2.内存占用3.网络IO4.磁盘IO1PartCPU占有率•CPU的高占用,并不一定表示有问题,因为实现最优性能的一方面就是充分发挥当前的硬件资源能力.但是CPU长期处于如图状态,就很值得我们关注,至少说明在大多数情况下,系统已经是在耗用最大的计算能力进行计算,运算能力已经成为瓶颈内存占用•内存在性能测试中是被重点关注的指标,因为它是反映重大缺陷——内存泄露的最直接指标,但是我们应该注意到,在JAVA框架中的内存泄漏是发生在虚拟内存中的。•观察内存/虚拟内存的占用情况,尤其是在压力消失后的内存占用恢复情况,是比较直接的判断内存泄漏的依据。内存占用如果观察到内存使用情况,在每次FullGC后,占用的内存都没能恢复到原来的水平,如果在压力撤除一段时间后,内存依旧不能恢复,那么十有八九当前系统存在内存泄漏。网络IO•很多时候大家都容易忽略网络对系统的影响,实际上网络带宽在一些情况下也会成为系统的瓶颈。一旦在业务的请求和响应中包含较大的数据传输时,往往会遇到网络瓶颈。因为更多的时候服务器采用的还是以太网卡,1000M网卡在全双工模式下传输速率也只有80M/s,如果响应中包含报表、图片之类的大尺寸数据,很有可能在性能测试中出现网络瓶颈。•还有一点就是不要忽略回环地址传输的影响,比如一些应用访问本地监听的其他服务,都会受到网卡的传输速率限制的影响磁盘IO•通常情况下,磁盘是计算机中速度最慢的一个子系统,因此很多情况中,磁盘I/O会成为系统的瓶颈。实际上在设计高性能系统的时候,会把避免磁盘I/O作为一个首要准则。•虽然当前的技术发展让存储系统的读写速度不断提升,但高昂的成本使得大多数情况下,高速存储会使用在数据库或文件服务器上,而不会使用在应用服务器中。所以在我们进行性能测试时,要更多的注意应用服务器的磁盘使用情况。性能瓶颈识别小结•一个项目的瓶颈,针对服务器来说,主要在CPU,内存,网络,磁盘。只有找到瓶颈的问题在哪里,我们才可以对症下药代码优化•代码优化1.编码技巧2.数据结构的合理选择3.IO的选择和使用2PartJVM运行时数据区RuntimeDataArea方法区MethodArea虚拟机栈VMStack本地方法栈NativeMethodStack堆Heap程序计数器ProgramCounterRegister由所有线程共享的数据区线程隔离的数据区内存分布对象的访问方式一般来说,一个Java的引用访问涉及到3个内存区域:JVM栈,堆,方法区。以最简单的本地变量引用:Objectobj=newObject()为例:•Objectobj表示一个本地引用,存储在JVM栈的本地变量表中,表示一个reference类型数据;•newObject()作为实例对象数据存储在堆中;•堆中还记录了Object类的类型信息(接口、方法、field、对象类型等)的地址,这些地址所执行的数据存储在方法区中;在Java虚拟机规范中,对于通过reference类型引用访问具体对象的方式并未做规定,目前主流的实现方式主要有两种:通过句柄访问的实现方式中,JVM堆中会专门有一块区域用来作为句柄池,存储相关句柄所执行的实例数据地址(包括在堆中地址和在方法区中的地址)。这种实现方法由于用句柄表示地址,因此十分稳定对象的访问方式通过直接指针访问的方式中,reference中存储的就是对象在堆中的实际地址,在堆中存储的对象信息中包含了在方法区中的相应类型数据。这种方法最大的优势是速度快,在HotSpot虚拟机中用的就是这种方式对象的访问方式编码技巧在了解对象访问方式和存储方式之后,我们就需要针对对象的存放和访问来书写和规范我们的代码。java面向对象创建使用销毁减少创建,调高效率,及时销毁编码技巧•尽量指定类、方法的final修饰符带有final修饰符的类是不可派生(最后的,最终的)的,不可被继承,final修饰符可以让方法不可以被重写。如果指定了一个类为final,则该类所有的方法都是final的。Java编译器会寻找机会内联所有的final方法,内联对于提升Java运行效率作用重大。确保唯一,不然就是子子孙孙无穷尽也编码技巧•尽量重用对象特别是String对象的使用,出现字符串连接时应该使用StringBuilder/StringBuffer代替。由于Java虚拟机不仅要花时间生成对象,以后可能还需要花时间对这些对象进行垃圾回收和处理,因此,生成过多的对象将会给程序的性能带来很大的影响。(String对象改变一次,新生成一个对象)。集合里创建对象,同理•尽量在合适的场合使用单例使用单例可以减轻加载的负担、缩短加载的时间、提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面:(1)控制资源的使用,通过线程同步来控制资源的并发访问(2)控制实例的产生,以达到节约资源的目的(3)控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信编码技巧•尽可能使用局部变量局部变量存储在栈中,速度较快,栈中创建的变量,随着方法的运行结束,这些内容就没了,不需要额外的垃圾回收。随用随取,用完就丢。•尽量减少对变量的重复计算明确一个概念,对方法的调用,存在消耗(栈帧,调用方法保护恢复现场)改为:编码技巧•乘法和除法使用移位操作用移位操作可以极大地提高性能,因为在计算机底层,对位的操作是最方便、最快的;编码技巧•尽量避免随意使用静态变量privatestaticBb=newB();A类不被卸载,那么引.用B指向的B对象会常驻内存,直到程序终止。•公用的集合类中不使用的数据一定要及时remove掉(内存泄漏的隐患)•静态类、单例类、工厂类将它们的构造函数置为private•尽量使用System.arraycopy()代替通过来循环复制数组•尽量避免使用二维数组课程内容•代码优化1.编码技巧2.数据结构的合理选择3.IO的选择和使用2Part数据结构的合理选择针对数据结构,主要讲解的是List,Set,MapList有序,可重复•ArrayList优点:底层数据结构是数组,查询快,增删慢。?缺点:线程不安全,效率高List有序,可重复•LinkedList优点:底层数据结构是链表,查询慢,增删快。?缺点:线程不安全,效率高List有序,可重复•如果列表容量足够(不用扩容),添加元素到尾部,推荐使用ArrayList,效率高于LinkedList。•除了一些极端的情况(首尾),添加元素到列表任意位置,LinkedList效率高于ArrayList。所以如果有在任意位置插入元素的需求,可以考虑LinkedList。注意:对于ArrayList来说,for循环的效率是最高的,iterator次之,forEach反而最低。LinkedList使用for循环完全没法忍受,原因是每次循环都会遍历一次列表;forEach会比iterator快SetSet里存放的对象是无序,不能重复的,集合中的对象不按特定的方式排序,只是简单地把对象加入集合中。HashSet无序,不可重复TreeSet有序,不可重复Set•HashSet哈希散列存储,通过哈希值来决定元素的存储位置,如果位置上没有对象,则存放;如果存在对象,则判断是否为同一对象,是则不存放,否则在下方顺延HashSet是基于HashMap来实现的,操作很简单,更像是对HashMap做了一次“封装”,而且只使用了HashMap的key来实现各种特性,而HashMap的value始终都是PRESENT。HashSet不允许重复(HashMap的key不允许重复,如果出现重复就覆盖),允许null值,非线程安全Set•HashSet内存泄漏hash集合在操作不当的情况下,有可能造成内存泄漏。考虑这样的情况,把一个对象存储进hashSet集合后,修改这个对象中参与计算hash的变量的值,这时这个对象的hash值也会随之改变,那么这么对象可以正常地被删除吗?否Set•TreeSetTreeSet是基于TreeMap实现的。TreeSet中的元素支持2种排序方式:自然排序或者根据创建TreeSet时提供的Comparator进行排序。这取决于使用的构造方法。TreeSet不支持快速随机遍历Map独立于Collection存在的接口,如下图:(简约图)Key|valueMap•HashMap链表散列(1.7)数组+链表+红黑树(1.8)Hash数组后边跟着小链表/红黑树Map•Hashtable与HashMap类似,不同的是:key和value的值均不允许为null;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢。Map•ConcurrentHashMap分片存储Map•TreeMapTreeMap基于红黑树(Red-Blacktree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的Comparator进行排序,具体取决于使用的构造方法选择技巧•尽量使用HashMap、ArrayList,除非线程安全需要,否则不推荐使用Hashtable、Vector,后两者由于使用同步机制而导致了性能开销(ConcurrentHashMap)•基于效率和类型检查的考虑,应该尽可能使用array,无法确定数组大小时才使用ArrayList•需要快速随机插入,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList选择技巧•可以借用set对list进行去重•使用最有效率的方式去遍历Map(entry)代码优化•代码优化1.编码技巧2.数据结构的合理选择3.IO的选择和使用2Part概念先来个例子理解一下同步,阻塞的概念,以银行取款为例:•同步:自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写);•异步:委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API);•阻塞:ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回);•非阻塞:柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)IO的选择和使用•BIO:同步、阻塞1.优点:代码简单、直观2.缺点:IO效率、扩展性存在局限性,会成为应用性能的瓶颈一个连接一个线程IO的选择和使用•NIO:同步、非阻塞1.可以构建多路复用的、同步非阻塞IO程序2.同时提供了更接近操作系统底层的高性能数据操作方式。一个请求一个线程IO的选择和使用•NIO2/AIO:异步、非阻塞1.引入了异步非阻塞IO方式2.异步IO操作基于事件和回调机制—我们不需要像NIO编程那样创建一个独立的I/O线程来处理读写操作,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理一个有效请求一个线程IO的选择和使用BIO、NIO、AIO适用场景分析:•BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。•NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。•AIO方式使用于连接数目多且连接比较长(重操作)的架构,比
本文标题:Java性能优化
链接地址:https://www.777doc.com/doc-7428945 .html