JVM(Java虚拟机)是Java程序的运行环境,它负责将Java程序编译后的字节码转换成机器码并执行。JVM的性能对Java程序的运行速度和稳定性有着至关重要的影响。本文将介绍JVM性能优化和调优的一些技巧和方法。
JVM的内存管理对程序的性能有着重要的影响。JVM的内存分为堆内存和非堆内存。堆内存用于存储对象实例,而非堆内存用于存储JVM本身的数据结构和代码。以下是一些内存管理的技巧:
调整堆内存大小:JVM的堆内存大小可以通过-Xmx和-Xms参数来调整。-Xmx参数用于设置JVM最大可用的堆内存大小,而-Xms参数用于设置JVM启动时的堆内存大小。如果JVM的堆内存过小,程序可能会频繁进行垃圾回收,从而影响程序的性能。如果堆内存过大,会导致程序启动缓慢和内存浪费。
使用垃圾回收器:JVM提供了多种垃圾回收器,每种垃圾回收器都有其优缺点。例如,CMS垃圾回收器适用于大型应用程序,而G1垃圾回收器适用于需要更好的响应时间的应用程序。选择合适的垃圾回收器可以提高程序的性能。
避免内存泄漏:内存泄漏是指程序中的对象无法被垃圾回收器回收,从而导致内存占用不断增加。内存泄漏会导致程序的性能下降和系统崩溃。避免内存泄漏的方法包括及时释放对象、避免循环引用等。
JVM的线程管理对程序的性能也有着重要的影响。以下是一些线程管理的技巧:
避免线程死锁:线程死锁是指两个或多个线程互相等待对方释放锁,从而导致程序无法继续执行。线程死锁会导致程序的性能下降和系统崩溃。避免线程死锁的方法包括避免循环等待、按照相同的顺序获取锁等。
使用线程池:线程池可以避免创建和销毁线程的开销,从而提高程序的性能。JVM提供了ThreadPoolExecutor类来实现线程池。
避免线程上下文切换:线程上下文切换是指CPU从一个线程切换到另一个线程的过程。线程上下文切换会导致程序的性能下降。避免线程上下文切换的方法包括减少线程数量、使用协程等。
代码优化是指通过改进代码结构和算法来提高程序的性能。以下是一些代码优化的技巧:
减少对象创建:对象的创建和销毁会占用CPU和内存资源。减少对象的创建可以提高程序的性能。例如,可以使用对象池来避免频繁创建对象。
使用高效的算法和数据结构:高效的算法和数据结构可以减少程序的运行时间和内存占用。例如,使用哈希表可以快速查找数据。
避免过度优化:过度优化可能会导致代码难以维护和理解。优化应该在保证代码可读性和可维护性的前提下进行。
选择合适的数据结构:选择 ArrayList 和 LinkedList 对我们的程序性能影响很大,为什么?因为 ArrayList 内部是数组实现,存在着不停的扩容和数据复制。
选择更优的算法
给定一个整数序列,a0, a1, a2, …… , an(项可以为负数),求其中最大的子序列和。如果所有整数都是负数,那么最大子序列和为 0;
例如(a[1],a[2],a[3],a[4],a[5],a[6])=(-2,11,-4,13,-5,-2)时,最大子段和为 20,子段为 a[2],a[3],a[4]。
n越大,时间就相差越大,比如 10000 个元素,最坏的算法和最好的算法之间的差距绝非多线程或者集群化能轻松解决的。
对JVM性能影响最大的是编译器。选择编译器是运行 java 程序首先要做的选择之一
对于程序来说,通常只有一部分代码被经常执行,这些关键代码被称为应用的热点,执行的越多就认为是越热。将这些代码编译为本地机器特定的二进制码,可以有效提高应用性能。
缺省编译器取决于机器位数、操作系统和 CPU 数目。32 位的机器上,一般默认都是client 编译,64 位机器上一般都是 server 编译,多核机器一般是 server 编译。
mix mode 一般指编译时机:
在编译后,会有一个代码缓存保存编译后的代码,一旦这个缓存满了,jvm 将无法继续编译代码。
当jvm提示: CodeCache is full,就表示需要增加代码缓存大小。-XX:ReservedCodeCacheSize=N 可以用来调整这个大小。
JVM类型 | 代码缓存的默认大小 |
---|---|
32位client Java8 | 32MB |
32位server,分层编译,Java8 | 240MB |
64位server , 分层编译,Java8 | 240MB |
32位client,Java7 | 32MB |
32位server,Java7 | 32MB |
64位server,Java7 | 48MB |
64位server,分层编译,Java7 | 96MB |
代码是否进行编译,取决于代码执行的频度,是否到达编译阈值。
计数器有两种:方法调用计数器和方法里的循环回边计数器,一个方法是否达到编译阈值取决于方法中的两种计数器之和。编译阈值调整的参数为:-XX:CompileThreshold=N
。
方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间之内方法被调用的次数。
当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会被减少一半,这个过程称为方法调用计数器热度的衰减(Counter Decay),而这段时间就称为此方法统计的半衰周期(Counter Half Life Time)。
进行热度衰减的动作是在虚拟机进行垃圾收集时顺便进行的,可以使用虚拟机参数-XX:-UseCounterDecay
来关闭热度衰减,让方法计数器统计方法调用的绝对次数,这样,只要系统运行时间足够长,绝大部分方法都会被编译成本地代码。
可以使用-XX:CounterHalfLifeTime
参数设置半衰周期的时间,单位是秒。与方法计数器不同,回边计数器没有计数热度衰减的过程,因此这个计数器统计的就是该方法循环执行的绝对次数。
进行代码编译的时候,是采用多线程进行编译的。
内联默认开启,-XX:-Inline,可以关闭,但是不要关闭,一旦关闭对性能有巨大影响。方法是否内联取决于方法有多热和方法的大小,
很热的方法如果方法字节码小于325字节才会内联,这个大小由参数 -XX:MaxFreqInlinesSzie=N
调整,但是这个很热与热点编译不同,没有任何参数可以调整热度。
方法小于35个字节码,一定会内联,这个大小可以通过参数-XX:MaxInlinesSzie=N
调整。
JVM 所做的最激进的优化,最好不要调整相关的参数。
目标:GC的时间够小,GC的次数够少,发生 Full GC 的周期足够的长,时间合理,最好是不发生。
使用各种JVM工具,查看当前日志,分析当前 JVM 参数设置,并且分析当前堆内存快照和gc日志,根据实际的各区域内存划分和 GC 执行时间,觉得是否进行优化。
如果各项参数设置合理,系统没有超时日志出现,GC频率不高,GC耗时不高,那么没有必要进行GC优化;如果GC时间超过1-3秒,或者频繁 GC,则必须优化;
如果内存分配过大或过小,或者采用的GC收集器比较慢,则应该优先调整这些参数,并且先找1台或几台机器进行 beta,然后比较优化过的机器和没有优化的机器的性能对比,并有针对性的做出最后选择。
尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择),在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
尽可能的设置大,可能到达GB的程度,因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。避免设置过小,当新生代设置过小时会导致:
年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数.如果堆设置小了,可以会造成内存碎片,高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。
最优化的方案,一般需要参考以下数据获得:
并发垃圾收集信息、持久代并发收集次数、传统 GC 信息、花在年轻代和年老代回收上的时间比例。
一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代,原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
JVM性能优化和调优是Java程序开发中的重要环节。通过合理的内存管理、线程管理和代码优化,可以提高程序的性能和稳定性。在进行优化时,应该根据具体情况选择合适的优化方法,并在保证代码可读性和可维护性的前提下进行优化。
全方面带你透彻探索服务优化技术方案