JVM详解及java 垃圾回收策略

文章开始之前,我们先明确一下两个知识点:java 堆、栈概念 和 java 内存的划分


一:java 堆栈概念

        从堆和栈的功能和作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的.而这种不同又主要是由于堆和栈的特点决定的




 Heap(堆   Stack(栈)
JVM中的功能 内存数据区 内存指令区
存储数据 对象实例 基本数据类型, 指令代码,常量,对象的引用地址


1. 保存对象实例,实际上是保存对象实例的属性值,属性的类型和对象本身的类型标记等,并不保存对象的方法(方法是指令,保存在stack中)。
       对象实例在heap中分配好以后,需要在stack中保存一个4字节的heap内存地址,用来定位该对象实例在heap中的位置,便于找到该对象实例。

 

2. 基本数据类型包括byte、int、char、long、float、double、boolean和short。
    函数方法属于指令.


二:java内存的划分

        整个JVM内存共划分为三个代:年轻代(Young Generation)、年老点(Old Generation)和持久代(Permanent Generation)


1:年轻代。所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个 Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能


2:年老代。在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象

3:持久代。基本固定不变,用于存放静态文件,例如java类和方法。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=<N>进行设置。


三:java GC收集器


        JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。


吞吐量优先的并行收集器


        并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。分为两种:一种并行收集器(-XX:+ UseParallelGC)在次要回收中使用多线程来执行,在主要回收中使用单线程执行;另一种是从Java 7u4开始默认使用的并行旧生代收集器(Parallel Old collector )(XX:+UseParallelOldGC),在次要回收和主要回收均使用多线程。当年老区填满后会触发主要回收。


典型配置:

java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20


-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。


-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。


java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC


-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。


java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC  -XX:MaxGCPauseMillis=100


-XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。


java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC  -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy


-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。


注意:在现在的硬件条件下,对年老代的压缩每GB的存活对象预计需要暂停一到五秒。


注意:在多插槽的CPU的服务器应用程序中设置“-XX:+ UseNUMA”,可以通过并行收集器能获得更好的性能。这是因为是在线程本地的CPU插槽上分配给Eden内存。遗憾的是其他收集器不支持这个功能。


响应时间优先的并发收集器

        并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。


CMS(并发标记清理收集器,Concurrent Mark Sweep)

CMS(-XX:+ UseConcMarkSweepGC)收集器在年老代中使用,专门收集那些在主要回收中不可能到达的年老对象。它与应用程序并发运行,在年老代中保持一直有足够的空间以保证不会发生年轻代晋升失败。


典型配置:

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC


-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。


-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。


java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection


-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。


-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片


晋升失败将会触发一次FullGC,CMS会按照下面几个步骤处理:

第一:初始化标记:寻找GC根。

第二:并发标记:标记所有从GC根开始可到达的对象。

第三:并发预清理:检查被更新过的对象引用和在并发标记阶段晋升的对象。

第四:重标记:捕捉预清洁阶段开始更新的对象引用。

第五:并发清理:通过回收被死对象占用的内存更新可用空间列表。

第六:并发重置:重置数据结构为下一次运行做准备。


四、young generation比例越大,不一定最好。

           将young的大小设置为大于总堆大小的一半时会造成效率低下。如果设置得过小,又会因为young generation收集程序不得不频繁运行而造成瓶颈。


五、总结

        从上面的推导可以得出很多结论,下面是经验总结与自已的认识

1.JVM堆的大小决定了GC的运行时间。如果JVM堆的大小超过一定的限度,那么GC的运行时间会很长。

2.对象生存的时间越长,GC需要的回收时间也越长,影响了回收速度。

3.大多数对象都是短命的,所以,如果能让这些对象的生存期在GC的一次运行周期内,wonderful!

4.应用程序中,建立与释放对象的速度决定了垃圾收集的频率。

5.如果GC一次运行周期超过3-5秒,这会很影响应用程序的运行,如果可以,应该减少JVM堆的大小了。

6.经验之谈:通常情况下,JVM堆的大小应为物理内存的80%。 


附录


  1. 常见配置汇总

    1. -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。

    2. -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

    3. -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。

    4. -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间

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

    6. -XX:+PrintGC

    7. -XX:+PrintGCDetails

    8. -XX:+PrintGCTimeStamps

    9. -Xloggc:filename

    10. -XX:+UseSerialGC:设置串行收集器

    11. -XX:+UseParallelGC:设置并行收集器

    12. -XX:+UseParalledlOldGC:设置并行年老代收集器

    13. -XX:+UseConcMarkSweepGC:设置并发收集器

    14. -Xms:初始堆大小

    15. -Xmx:最大堆大小

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

    17. -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4

    18. -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5

    19. -XX:MaxPermSize=n:设置持久代大小

    20. 堆设置

    21. 收集器设置

    22. 垃圾回收统计信息

    23. 并行收集器设置

    24. 并发收集器设置


  2. 调优总结

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


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


  • -XX:MaxHeapFreeRatio=30


  • 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:减少年轻代和年老代花费的时间,一般会提高应用的效率


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


  • 并发垃圾收集信息


  • 持久代并发收集次数


  • 传统GC信息


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


  • 响应时间优先的应用尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。


  • 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。


  1. 年轻代大小选择



  2. 年老代大小选择

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


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

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

    -XX:MaxHeapFreeRatio=30


猜你喜欢

转载自blog.csdn.net/timy07/article/details/80282313