java虚拟机(六)--垃圾收集器和内存分配策略

  目前没有完美的收集器,不同的厂商、版本的虚拟机提供的垃圾收集器会有很大的差别,用户根据自己应用特点和要求组合出各个年代所使用

的收集器。基于jdk1.7Update14之后的虚拟机。

官方文档:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/toc.html

HotSpot的垃圾收集器

上图是作用于新生代和老年代的收集器,连线代表可以搭配使用。

分类:

1、串行收集器:Serial、Serial Old

  GC的时候需要暂停其他线程的工作,启动一个GC线程进行垃圾回收

2、并行收集器:Parallel Scavenge、Parallel Old,吞吐量优先

  指的是多条垃圾收集线程并行工作,但是用户线程还是被暂停了。适合科学计算、后台处理等弱交互场景

3、并发收集器:CMS、G1,停顿时间优先

  指的是垃圾收集线程和用户线程同时执行(不一定是并行,可以是交替运行),不会出现Stop-the-world。适合对响应时间有要求的场景,如

web应用。

停顿时间:

  垃圾收集器在GC的时候中断应用程序的时间。-XX:MaxGCPauseMills

吞吐量:

  这里的吞吐量为GC时间和应用运行的时间的比值。-XX:GCTimeRatio=<n>,GC时间占比为1/(1+n)

新生代采用复制算法,而老年代采用标记-整理、标记-清除算法

1、Serial:串行

  采用复制算法的单线程收集器,进行GC的时候需要暂停其他线程的工作(被称为Stop The World),直到它收集结束。Serial依然是运行在

Client模式下的默认新生代收集器,因为简单、高效。用户桌应用场景中,分配给虚拟机管理的内存一般来说不会很大,收集几十兆甚至一两百

兆的新生代停顿时间在几十毫秒最多一百毫秒,只要不是频繁发生,这点停顿是完全可以接受的。单个CPU环境下比较适合,Web应用不可能使用。

  -XX:+UseSerialGC  -XX:+UseSerialOldGC

2、ParNew

  ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集外,其余行为和Serial收集器完全一样,一些参数和回收策

略都相同。它是Server模式下的首选的新生代收集器,因为除了Serial收集器外,目前只有它能与CMS收集器配合工作。它默认开启的收集线程数与

CPU数量相同,在CPU数量非常多的情况下,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。

3、Parallel Scavenge

  新生代,用复制算法,并行的多线程收集器,CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel收集器的目标则

是打到一个可控制的吞吐量。另外,Parallel收集器是虚拟机运行在Server模式下的默认垃圾收集器。

  停顿时间短适合需要与用户交互的程序,良好的响应速度能提升用户体验;高吞吐量则可以高效率利用CPU时间,尽快完成运算任务,主要适合

后台运算而不需要太多交互的任务。

通过以下命令查看当前运行的jvm启用的是否为Parallel垃圾回收器

[root@iZuf6fkfhthmdm1nwdg5isZ bin]# jinfo -flag UseParallelGC 24642
-XX:+UseParallelGC
[root@iZuf6fkfhthmdm1nwdg5isZ bin]# jinfo -flag UseParallelOldGC 24642
-XX:+UseParallelOldGC

Server模式:

  jvm自动判断机制,根据当前系统CPU和内存等情况,决定启动Server模式,还是Client模式。一般内存>2G,就是Server模式了

  -XX:+UseParallelGC  -XX:+UseParallelOldGC

-XX:ParallelGCThreads=<n>多少个线程,CPU>8 N=5/8,CPU<8 N=CPU

4、Serial Old

  Serial收集器的老年代版本,同样是一个单线程收集器,使用“标记-整理算法”,这个收集器的主要意义也是在于给Client模式下的虚拟机使用。

5、Parallel Old

  Parallel收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器在JDK 1.6之后的出现,“吞吐量优先收集器”终于有了比较

名副其实的应用组合,在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel收集器+Parallel Old收集器的组合。

6、CMS:Concurrent Mark Sweep 非常适用B/S系统

  以获取最短GC停顿时间为目标的老年代收集器。目前很大一部分Java应用集中在互联网站或者B/S系统的服务端上,注重服务的响应速度,希望

统停顿时间最短,以给用户带来较好的体验,CMS收集器就非常符合这类应用的需求。CMS收集器基于“标记-清除”算法实现的。

  -XX:+UserConcMarkSweepGC  -XX:+UseParNewGC

我们在Catalina.xml中通过JAVA_OPTS设置:-XX:+UserConcMarkSweepGC,然后查看

[root@iZuf6fkfhthmdm1nwdg5isZ bin]# jinfo -flag UseConcMarkSweepGC 29720
-XX:+UseConcMarkSweepGC
[root@iZuf6fkfhthmdm1nwdg5isZ bin]# jinfo -flag UseParNewGC 29720
-XX:+UseParNewGC

运行过程分为四步:

  初始标记:会有GC停顿,只是标记GC Root,速度很快,会发生Stop-The-Word

  并发标记:进行GC Roots Tracing的过程

  并发预清理

  重新标记:会有GC停顿,修正并发标记期间标记产生变动的那部分对象的标记,会发生Stop-The-Word

  并发清除:

  并发重置:

  并发标记、并发清除是耗时最长的但是可以与用户线程同时工作

缺点:

  1、CPU敏感,占用CPU资源,应用程序吞吐量下降

  2、并发清理阶段用户线程还在运行,就会产生浮动垃圾,由于标记过了,只能下次GC才能清理

  3、采用"标记-清除"算法就会产生大量空间碎片,大对象分配很麻烦,可能提前Full GC

相关参数:

-XX:ConcGCThreads:与应用程序并发执行的GC线程数,不是Stop-the-world的线程数
-XX:+UseCMSCompactAtFullCollection:Full GC之后进行压缩,因为会产生碎片
-XX:CMSFullGCsBeforeCompaction:发生多少次Full GC压缩一次
-XX:CMSInitiatingOccupancyFraction:Old区内存使用达到多少,才会触发Full GC,默认92%
-XX:+UseCMSInitiatingOccupancyOnly:是否可以冬天调整
-XX:CMScavengeBeforeRemark:Full GC之前先做Young GC,一般开启
-XX:+CMSClassUnloadingEnabled:启用回收Perm区

7、G1(Garbage-First):兼顾吞吐量和停顿时间的GC实现,是JDK9以后的默认GC选项,新生代和老生代的收集器

  JDK 7 Update 4后开始进入商用。使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆分为多个大小相等的独立

区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region的集合。G1收集器跟踪各

个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也是Garbage-First

名称的由来)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。

描述:

Region:

  上图为G1收集器的样式,每一个小块就是一个Region,一部分Region组成Young区,一部分组成Old区,Young和Old只是逻辑的分布,其中标明

H的Region是专门用来保存大对象的,超过Region大小的一半,就会把对象存放到H Region中。

SATB:

  Snapshot-At-The-Begining,通过GC-Root得到,GC开始时存活的快照。

RSet:

  记录其他Region中对象引用自身Region中对象的关系,属于points-into结构(谁引用了我的对象)。

Young GC:

  1、新对象进入Eden区

  2、存活对象拷贝到Survivor区

  3、存活时间到达年龄阀值,晋升到Old区

MixedGC:

  1、不是Full GC,而是回收所有的Young和部分Old

  2、这个过程会有global concurrent marking

global concurrent marking:

  1、初始标记:会有GC停顿,只是标记GC Root,速度很快,会发生Stop-The-Word

  2、并发标记:标记存活的Region

  3、标记存活的对象

  4、重新标记:会有GC停顿,修正并发标记期间标记产生变动的那部分对象的标记,会发生Stop-The-Word

  5、并发清除:部分发生Stop-The-Word

Mixed GC发生的时机:

InitiatingHeapOccupancyPercent:

  堆占有率达到这个数值则触发global concurrent marking,默认45%

G1HeapWastePercent:

  在global concurrent marking结束之后,可以知道区中有多少空间要被回收,在每次YGC之后和再次发生Mixed GC之前,会检查垃圾占比是

否达到这个参数,如果达到了,下次才会发生Mixed GC。

G1MixedGCLiveThresholdPercent:

  Old区Region被回收的时候,存货对象的占比

G1MixedGCCountPercent:

  一次global concurrent marking之后,最多执行Mixed GC的次数

可以参考:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#g1_gc_tuning

官方文档把所有的内容写得都很详细,英语不好的话就翻译过来看,尽量原文看,还能学习英语

G1最佳实践:

1、年轻代大小:

  避免使用-Xmn、-XX:NewRatio等显式设置Young区大小,会覆盖暂停时间目标

2、暂停时间目标:

  暂停时间不要太过严苛,吞吐量目标是90%应用程序时间和10%的垃圾回收时间,太严苛会影响到吞吐量

需要切换到G1垃圾回收器的条件:

  1、堆的50%被存货对象占用

  2、对象分配和晋升的速度变化很大

  3、垃圾回收时间很长,超过1S

PS:-XX:+UseG1GC,jdk8之后,推荐使用G1垃圾回收器,吞吐量比较高,目标是在于大内存6G+,停顿时间小于0.5s

与其他GC收集器相比,G1的特点:

1).并发与并行

  充分利用多CPU、多核环境下的硬件优势,来缩短Stop-The-World停顿时间,G1可以通过并发让java程序继续执行

2).分带收集

  通过不同的方式处理新创建的对象和存货了一段时间、熬过多次GC的就对象来获取更好的收集效果

3).空间整合

  G1从整体来看基于“标记-整理”算法实现的收集器,从局部(两个Region之间)看基于“复制”算法实现

而且这两种算法不会产生内存空间碎片,收集后能提供规整的可用内存

4).可预测的停顿

  G1处理追求低停顿,还能建立可预测的停顿时间模型,让使用者明确指定在一个长度为Mms的时间片段内,消耗在GC的时间不超过Nms,几乎已经

是实时Java的垃圾收集器的特征了。

  Region之间对象引用以及其他收集器中的新声代和老年代之间的对象引用,虚拟机都是使用Rememberd Set来避免全堆扫描。

如果不计算维护Rememberd Set的操作,收集步骤如下:

  1).初始标记

  2).并发标记

  3).最终标记

  4).筛选回收

如何选择垃圾回收器?

1、优先调整堆的大小让服务器自己选择

2、如果内存小于100M,使用串行收集器

3、如果是单核,并且没有停顿时间的要求,选择串行或者服务器自己选择

4、如果允许停顿时间超过1S,选择并行或者服务器自己选择

5、如果响应时间很重要,并且不能超过1S,使用并发收集器

可参考官方文档:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/collectors.html

想要对这部分内容很了解,官方文档一定要多读几遍。。。

Full GC触发条件:

(1)调用System.gc时,系统建议执行Full GC,但是不是必然执行

(2)老年代空间不足

(3)方法区空间不足

(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存

(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对

象大小

内存分配与回收策略:

内存分配的规则取决于使用的是哪一种垃圾回收收集器组合,还有虚拟机中与内存相关参数的配置

1、对象优先Eden space分配,如果Eden Space没有足够的空间,会发生一次Minor GC

2、大对象(很长的字符串、数组)直接分配得到老年代,通过参数-XX:PretenureSizeThreshold=3145728(3M),只要大于这个值,对象直接分

配到老年代,防止新生代进行大量内存复制

3、长时间存活的对象,会转到老年代,每个对象有个年龄计数器,在survivor每次经过一次Minor GC,就会加一,默认15的时候,会分到老年代

参数为:-XX:MaxTenuringThreshold

4、第三条不是绝对的,VM会动态判断

  -XX:TargetSurvivorRatio:如果进行进行一次GC存活的对象超过这个参数,会计算相同年龄所有对象大小的综合大于Survivor空间的一半,

年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold要求的年龄

空间担保失败:

  发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果成立,可以确保Minor GC是安全的,else

检查HandlerPromotionFailure设置值是否允许担保失败。if许,继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小

,true,查实进行一次Minor GC,尽管有风险,如果小于,或者参数设置不允许,改为进行一次Full GC但是HandlerPromotionFailure在jdk 6

Update 24没有作用了,jdk规则变成只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则进行Full GC

posted @ 2019-05-08 18:01  Diamond-Shine  阅读(462)  评论(0编辑  收藏  举报