JVM 通用参数调优

1      JVM影响的因素

一、以下配置主要针对分代垃圾回收算法而言 堆大小设置,JVM最大堆大小有以下几个限制方面

[1]      操作系统

       相关操作系统的数据模型(32-bit还是64-bit)限制,系统的可用虚拟内存限制,系统的可用物理内存限制,在32位系统下,一般限制在1.5G-2G,64位理论上是无限扩大内存的,但是一般取决于自己应用程序,大体控制在4G左右就可以了

[2]      JDK版本

       不同的JDK版本设置的堆内存大小是不同,JDK5.0下测试最大可为1487mJDK6.0以后可设置为3550m左右

2       简单介绍堆栈的名词常量已经设置

堆设置

1Xms: 初始化堆大小

2)–Xmx: 最大堆大小

3-XX:NewSize=n:设置年轻代大小

4-XX:NewRatio=n:设置年轻代和年老代的比值 如:为3,表示年轻代与年老代比值为1:3.年轻代年老代和的1/4 Xmx设为4G,那年轻代的堆内存大小4G*(1/4),年老代为4G*(3/4)

5-XXSurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值,注意Survivor区有两个,Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5,例如:年轻代共1G大小,则Eden区则为1G*3/5,一个Survivor1G*1/5

(6)  -XX:MaxPermSize=n:设置持久代大小

 

 收集器设置

(1)       -XX:+UseSerialGC:设置串行收集器 

(2)       XX:+UseParallelGC:设置并行收集器

(3)        -XX:UseParalledlOldGC:设置并行年老代收集器

(4)        -XX:+UseConcMarkSweepGC:设置并发收集器

垃圾回收统计信息

-XX:+PrintGC

-XX:+PrintGCDetails

 -XX:+PrintGCTimeStamps

-Xloggc:filename

并行收集器设置

-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数,并行收集线程数 -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间。

 -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

 

3      调优总结

年轻代大小选择 

   响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)在此情况下,年轻代收集发生的频率也是最小的,同时,减少到达年老代的对象   吞吐量优先的应用:尽可能的设置大,可能到达的Gbit的程度,因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。

年老代大小的选择  
   
响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和回话持续时间等一些参数,如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要教程的收集时间,最优化的方案,一般需要参考一下数据获得:
       1
 并发垃圾收集信息

       2 持久代并发收集次数

       3 传统GC信息

       4 花在年轻代和年老代的回收上的时间比例

减少年轻代和年老代花费的时间,一般会提高应用的效率。

 

吞吐量优先的应用

       一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代,原因是这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽可能存放长期存活对象。

较小堆引起的碎片问题

       以年老代的并发收集器使用比较、清楚算法,所以不会堆进行压缩,当收集器回收时,它会把相邻的空间进行合并,这样可以分配给较大的对象,但是当堆空间较小时,运行一段时间以后,就会出现碎片,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收,如果出现碎片,可能需要进行如下配置:

  1 XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩

  2-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩。

 

垃圾回收的瓶颈

        传统分代垃圾回收方式,已经在一定程度上把垃圾回收给应用代来的负担降低到了最小,把应用的吞吐量推到了一个极限,但是他无法解决一个问题,就是Full GC所带来的应用暂停,在一些对实时性要求很高的应用场景下,GC暂停所带来的请求堆积和请求失败是无法接受的,这类应用可能要求请求的返回时间在几百甚至几十毫秒以内,如果分代垃圾回收方式要达到这个指标,只能把最大堆得设置限制在一个相对较小的范围内,但是这样限制了应用本身的处理能力,同样也是不可接收的   分代垃圾回收方式确实也考虑了实时性要求而提供了并发回收期,支持最大暂停时间的设置,但是受限于分代垃圾回收的内存划分模型,其效率也不是很理想。   为了达到实时性的要求(其实java语言最初的设计也是嵌入式系统上的),一种新垃圾回收方式呼之欲出,它即支持短的暂停时间,又支持大得内存空间分配,可以很高的解决传统的分代方式带来的问题

 

增量收集的演进 

       增量收集的方式理论上可以解决传统分代方式代来的问题,增量收集把堆空间划分成一系列的内存块,使用时,先使用其中一部分(不会全部用完),垃圾收集时把之前用掉的部分中的存活对象再放到后面没有用的空间中,这样可以实现一直边使用边收集的效果,避免了传统分代方式整个使用完了再暂停的回收的情况。 当然传统分代收集方式也提供了并发收集,但是他有一个致命的地方,就是把整个堆作为一个内存块,这样一方面会造成碎片(无法压缩),另外一个方面他得每次收集都是对整个堆得收集,无法进行选择,在暂停时间的控制上还是很弱,而增量方式,通过内存空间的分块,恰恰可以解决上面的问题

 

4      常见问题

1 持久代被占满

异常:java.lang.OutOfmemeoryError:PermGen space

       说明:perm空间被占满,无法为新的calss分配存储空间而引发的异常,这个异常以前是没有的,但是在java反射大量使用的今天这个异常比较常见了,主要原因就是大量动态繁盛生成的类不断被加载,最终导致Perm区被占满,更可怕的是,不同的ClassLoader即便使用了相同的类,但是都回对其进行加载,相当于同一个东西,如果有NclassLoader那么他将会被加载N此,因此,某些情况下,这个问题基本视为无解,当然存在大量classLoader和大量反射类的情况其实也不多。

解决: -XX:MaxPermSize=16m 换用JDK 比如JRocke

2 堆栈溢出

异常:java.lang.StackOverflowError

说明:这个就不多说了,一般就是递归没有返回,或者循环调用造成

 

3 线程堆栈满

       异常:Fatal:Stack size too small 

       说明:java中一个线程的空间大小时有限制的,JDK5.0以后这个值是1M,与这个线程相关的数据将会保存在当中,但是当线程空间满了以后,将会出现上面异常。

 解决:增加线程栈大小,-Xss2m,

但这个配置无法解决根本问题,还要看代码部分是否有造成泄漏的部

 

4 系统内存被占满 

异常:java.lang.OutOfMemoryError:unable to create new native thread

 说明:这个异常是由于操作系统没有足够的资源来产生这个线程造成的,系统创建线程时,除了要在java堆中分配内存外,操作系统本身也需要分配资源来创建线程,因此,当线程数量达到一定程度以后,堆中或许还有空间,到那时操作系统分配不出资源来了,就出现这个异常了,分配给java虚拟机的内存愈多,系统剩余的资源就越少,因此,当系统内存固定时,分配给java虚拟机的内存越多,那么,系统总共能够产生线程也就越少,两者成反比的关系,同时,可以通过修改-Xss来减少分配给单个线程的空间,也可以增加系统总共内生产的线程数。 解决:重新设计系统减少线程数量。  线程数量不能减少的情况下,通过-Xss减少单个线程大小,以便能生产更多的线程。

 

 

       同一机器部署多个JVM 

   这也是一种很好的方式,可以分为纵拆和横拆,纵拆可以理解为把java应用划分为不同模块,各个模块使用一个独立的java进程,而横拆则是同样供的应用部署多个JVM 通过部署多个JVM,可以把每个JVM的内存控制衣蛾垃圾回收可以忍受的范围内即可,但是这相当于进行了分布式的处理,其额外带来的复杂性也是需要评估的,另外也有支持分布式的这种JVM可以考虑,这种方式是理想当中的方式,目前的虚拟机还没有,纯属假设,即:考虑由编程方式配置哪些对象在垃圾收集过程中可以直接跳过,减少垃圾回收线程遍历标记的时间,这种方式相当于在编程的时候告诉虚拟机某些对象你可以在某时间后再进行收集或者又u代码表示可以收集了,在这之前你即去遍历他也是没有效果的,他肯定是还在被引用的。  这种方式如果JVM可以实现,那么java即有了垃圾回收的优势,又有了c\c++对内存的可空性。  

 线程分配

  Java的阻塞式线程模型基本上可以抛弃了,目前成熟的NIO框架也比较多了,阻塞式IO带来的问题是线程数量的线性增长,而NIO则可以转换成为常数线程,因此对于服务端的应用而言,NIO还是唯一选择,不过JDK7中为我们带来的AIO是否能让人眼前一亮呢?

 其他JDK 

  本文说的都是SunJDK,目前常见的JDK还有JRocketIBMJDK。其中JRocketIO方面比Sun的高很  多,不过Sun JDK6.0以后提高也很大。而且JRocket在垃圾回收方面,也具有优势,其可设置垃圾回收的最大  暂停时间也是很吸引人的。不过,系统SunG1实现以后,在这方面会有一个质的飞跃。


JVM一些简单设置

 

1JVM影响的因素

一、以下配置主要针对分代垃圾回收算法而言 堆大小设置,JVM最大堆大小有以下几个限制方面

[1]操作系统

相关操作系统的数据模型(32-bit还是64-bit)限制,系统的可用虚拟内存限制,系统的可用物理内存限制,在32位系统下,一般限制在1.5G-2G,64位理论上是无限扩大内存的,但是一般取决于自己应用程序,大体控制在4G左右就可以了

[2]JDK版本

不同的JDK版本设置的堆内存大小是不同,JDK5.0下测试最大可为1487mJDK6.0以后可设置为3550m左右

2简单介绍堆栈的名词常量已经设置

堆设置

1Xms: 初始化堆大小

2)–Xmx: 最大堆大小

3-XX:NewSize=n:设置年轻代大小

4-XX:NewRatio=n:设置年轻代和年老代的比值 如:为3,表示年轻代与年老代比值为1:3.年轻代年老代和的1/4 Xmx设为4G,那年轻代的堆内存大小4G*(1/4),年老代为4G*(3/4)

5-XXSurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值,注意Survivor区有两个,Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5,例如:年轻代共1G大小,则Eden区则为1G*3/5,一个Survivor1G*1/5

(6) -XX:MaxPermSize=n:设置持久代大小

 

收集器设置

(1)-XX:+UseSerialGC:设置串行收集器 –

(2)XX:+UseParallelGC:设置并行收集器

(3) -XX:UseParalledlOldGC:设置并行年老代收集器

(4) -XX:+UseConcMarkSweepGC:设置并发收集器

垃圾回收统计信息

-XX:+PrintGC

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-Xloggc:filename

并行收集器设置

-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数,并行收集线程数 -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间。

-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

3调优总结

年轻代大小选择

响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)在此情况下,年轻代收集发生的频率也是最小的,同时,减少到达年老代的对象吞吐量优先的应用:尽可能的设置大,可能到达的Gbit的程度,因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。

年老代大小的选择 
响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和回话持续时间等一些参数,如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要教程的收集时间,最优化的方案,一般需要参考一下数据获得:
1 并发垃圾收集信息

2 持久代并发收集次数

3 传统GC信息

4 花在年轻代和年老代的回收上的时间比例

减少年轻代和年老代花费的时间,一般会提高应用的效率。

吞吐量优先的应用

一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代,原因是这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽可能存放长期存活对象。

较小堆引起的碎片问题

以年老代的并发收集器使用比较、清楚算法,所以不会堆进行压缩,当收集器回收时,它会把相邻的空间进行合并,这样可以分配给较大的对象,但是当堆空间较小时,运行一段时间以后,就会出现碎片,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收,如果出现碎片,可能需要进行如下配置:

1 XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩

2-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩。

 

垃圾回收的瓶颈

传统分代垃圾回收方式,已经在一定程度上把垃圾回收给应用代来的负担降低到了最小,把应用的吞吐量推到了一个极限,但是他无法解决一个问题,就是Full GC所带来的应用暂停,在一些对实时性要求很高的应用场景下,GC暂停所带来的请求堆积和请求失败是无法接受的,这类应用可能要求请求的返回时间在几百甚至几十毫秒以内,如果分代垃圾回收方式要达到这个指标,只能把最大堆得设置限制在一个相对较小的范围内,但是这样限制了应用本身的处理能力,同样也是不可接收的分代垃圾回收方式确实也考虑了实时性要求而提供了并发回收期,支持最大暂停时间的设置,但是受限于分代垃圾回收的内存划分模型,其效率也不是很理想。为了达到实时性的要求(其实java语言最初的设计也是嵌入式系统上的),一种新垃圾回收方式呼之欲出,它即支持短的暂停时间,又支持大得内存空间分配,可以很高的解决传统的分代方式带来的问题

 

增量收集的演进

增量收集的方式理论上可以解决传统分代方式代来的问题,增量收集把堆空间划分成一系列的内存块,使用时,先使用其中一部分(不会全部用完),垃圾收集时把之前用掉的部分中的存活对象再放到后面没有用的空间中,这样可以实现一直边使用边收集的效果,避免了传统分代方式整个使用完了再暂停的回收的情况。 当然传统分代收集方式也提供了并发收集,但是他有一个致命的地方,就是把整个堆作为一个内存块,这样一方面会造成碎片(无法压缩),另外一个方面他得每次收集都是对整个堆得收集,无法进行选择,在暂停时间的控制上还是很弱,而增量方式,通过内存空间的分块,恰恰可以解决上面的问题

 

4常见问题

1 持久代被占满

异常:java.lang.OutOfmemeoryError:PermGen space

说明:perm空间被占满,无法为新的calss分配存储空间而引发的异常,这个异常以前是没有的,但是在java反射大量使用的今天这个异常比较常见了,主要原因就是大量动态繁盛生成的类不断被加载,最终导致Perm区被占满,更可怕的是,不同的ClassLoader即便使用了相同的类,但是都回对其进行加载,相当于同一个东西,如果有NclassLoader那么他将会被加载N此,因此,某些情况下,这个问题基本视为无解,当然存在大量classLoader和大量反射类的情况其实也不多。

解决: -XX:MaxPermSize=16m 换用JDK 比如JRocke

2 堆栈溢出

异常:java.lang.StackOverflowError

说明:这个就不多说了,一般就是递归没有返回,或者循环调用造成

 

3 线程堆栈满

异常:Fatal:Stack size too small

说明:java中一个线程的空间大小时有限制的,JDK5.0以后这个值是1M,与这个线程相关的数据将会保存在当中,但是当线程空间满了以后,将会出现上面异常。

解决:增加线程栈大小,-Xss2m,

但这个配置无法解决根本问题,还要看代码部分是否有造成泄漏的部

 

4 系统内存被占满

异常:java.lang.OutOfMemoryError:unable to create new native thread

说明:这个异常是由于操作系统没有足够的资源来产生这个线程造成的,系统创建线程时,除了要在java堆中分配内存外,操作系统本身也需要分配资源来创建线程,因此,当线程数量达到一定程度以后,堆中或许还有空间,到那时操作系统分配不出资源来了,就出现这个异常了,分配给java虚拟机的内存愈多,系统剩余的资源就越少,因此,当系统内存固定时,分配给java虚拟机的内存越多,那么,系统总共能够产生线程也就越少,两者成反比的关系,同时,可以通过修改-Xss来减少分配给单个线程的空间,也可以增加系统总共内生产的线程数。 解决:重新设计系统减少线程数量。线程数量不能减少的情况下,通过-Xss减少单个线程大小,以便能生产更多的线程。

 

 

同一机器部署多个JVM

这也是一种很好的方式,可以分为纵拆和横拆,纵拆可以理解为把java应用划分为不同模块,各个模块使用一个独立的java进程,而横拆则是同样供的应用部署多个JVM 通过部署多个JVM,可以把每个JVM的内存控制衣蛾垃圾回收可以忍受的范围内即可,但是这相当于进行了分布式的处理,其额外带来的复杂性也是需要评估的,另外也有支持分布式的这种JVM可以考虑,这种方式是理想当中的方式,目前的虚拟机还没有,纯属假设,即:考虑由编程方式配置哪些对象在垃圾收集过程中可以直接跳过,减少垃圾回收线程遍历标记的时间,这种方式相当于在编程的时候告诉虚拟机某些对象你可以在某时间后再进行收集或者又u代码表示可以收集了,在这之前你即去遍历他也是没有效果的,他肯定是还在被引用的。这种方式如果JVM可以实现,那么java即有了垃圾回收的优势,又有了c\c++对内存的可空性。线程分配

Java的阻塞式线程模型基本上可以抛弃了,目前成熟的NIO框架也比较多了,阻塞式IO带来的问题是线程数量的线性增长,而NIO则可以转换成为常数线程,因此对于服务端的应用而言,NIO还是唯一选择,不过JDK7中为我们带来的AIO是否能让人眼前一亮呢?

其他JDK

本文说的都是SunJDK,目前常见的JDK还有JRocketIBMJDK。其中JRocketIO方面比Sun的高很多,不过Sun JDK6.0以后提高也很大。而且JRocket在垃圾回收方面,也具有优势,其可设置垃圾回收的最大暂停时间也是很吸引人的。不过,系统SunG1实现以后,在这方面会有一个质的飞跃

猜你喜欢

转载自blog.csdn.net/denglixin118/article/details/72228856