JVM堆内存与垃圾回收

标签:

本文出自jvm123.com-java技术分享站:http://jvm123.com/2019/11/jvm-dui-nei-cun-yu.html

JVM堆内存

JVM堆内存结构

JVM堆内存与垃圾回收插图

JVM堆内存分配和内存回收过程

年轻代的比例可以是:8:1:1;年轻代和老年代的比例可以是:1:3

  1. 新生对象分配在eden区,超过eden区大小的大对象,直接分配在老年代;
  2. 新生对象占满eden区之后,继续分配时,则会触发GC(monitor GC或young GC,复制算法、可用并行垃圾回收器),将存活的对象放入s0区。同样的过程之后,下次young GC被触发时,将所有存活的对象复制在s1区。
  3. 新生代的垃圾回收比较频繁,对象存活率低,所以使用复制算法,吧存活对象在s0和s1区反复复制,直到对象的存活年龄超过一定的阈值,就会放入老年代。
  4. 所以,老年代存放的对象一般都是经过多次都没被回收的,如果进行GC,对象的存活率比较高。
  5. 当老年代的空间不够用时,会触发Full GC/Major GC, Full GC使用标记-整理算法,可用串行垃圾收集器,会stop the world,所以尽量避免频繁执行Full GC。

垃圾回收算法

复制算法

将存活对象复制到一块新的内存区域即可。eg:新生代垃圾回收时,将eden区和s0区存活的对象复制到s1区。

标记-整理算法

将存活对象标记,然后移动到内存区域的一端。

对象存活的判断

引用计数

每次引用一个对象,就计数加1,计数为0时,就成为垃圾。这个方法无法解决循环引用的问题。

可达性分析

GC root 可以到达的对象都为存活对象。

可以作为GC Roots的对象包括:

  • 虚拟机栈中引用的对象(局部变量)
  • 方法区中类静态属性引用的对象(静态变量)
  • 方法区中常量引用的对象(常量)
  • 本地方法栈中JNI(即本地方法)引用的对象

垃圾回收器

JVM堆内存与垃圾回收插图(1)

新生代

工作区域线程数垃圾收集算法
Serial收集器新生代单线程复制算法Client模式下的默认新生代收集器。进行垃圾收集时,必须Stop the world,直到它收集结束。-XX:+UseSerialGC
ParNew收集器(Serial收集器的多线程版本)新生代多线程复制算法Server模式下的默认新生代收集器
Parallel Scavenge收集器新生代多线程复制算法吞吐量优先收集器目标:控制吞吐量吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)吞吐量越高说明CPU时间利用率越高。JVM参数:-XX:MaxGCPauseMillis 控制最大垃圾收集时间-XX:GCTimeRatio 控制吞吐量大小-XX:+UseParallelGC -XX:+UseParallelOldGC

老年代

作用区域单线程/多线程垃圾收集算法
Serial Old收集器(Stop the World)老年代单线程标记整理算法这个收集器的主要意义也是在于给Client模式下的虚拟机使用。 如果在Server模式下,主要两大用途:(1)在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用(2)作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用
Parallel Old收集器(Stop the World)老年代多线程标记整理算法Parallel Old 是Parallel Scavenge收集器的老年代版本。这个收集器在1.6中才开始提供。在JDK1.5以及之前的版本中,Parallel Scavenge+Serial Old(单线程),无法充分利用多CPU的处理能力。1.6之后,终于有了名副其实的“吞吐量优先“收集器组合:Parallel Scavenge + Parallel Old。
CMS收集器(Concurrent Mark Sweep)老年代多线程标记-清除算法目标:最短回收停顿时间优点:并发收集,低停顿缺点:1)CPU资源敏感。在并发阶段,虽然不会导致用于线程停顿。但会因为占用了一部分CPU资源,导致用户应用程序变慢,吞吐量降低。2)无法处理浮动垃圾并发清理阶段,用户线程仍旧在运行并产生垃圾,这些产生的垃圾此次收集无法清理。因此,需要预留一部分内存用于并发清理时用户程序使用。解决方法:当预留的内存不够时,将发生Concurrent Mode Failure,JVM启动后备预案,临时启用Serial Old进行老年代垃圾收集-XX:CMSInitiatingOccupancyFraction3)内存碎片问题解决方法:-XX:CMSFullGCsBeforeCompaction 设置执行多少次不带压缩的Full GC后,运行一次带压缩的(默认为0,表示每次Full GC都进行碎片整理)

G1垃圾收集

JVM堆内存与垃圾回收插图(2)

G1垃圾收集器

G1收集器将java堆均分成大小相同的区域(region,1M-32M,最多2000个,最大支持堆内存64G)。一个或多个不连续的区域共同组成eden、survivor或old区,但大小不再固定,这为内存应用提供了极大地弹性。G1垃圾收集过程与CMS类似。G1在堆内存中并发地对所有对象进行标记,决定对象的可达性。经过全局标记,G1了解哪些区域几乎是空的,然后优先收集这些区域,这就是GarbageFirst的命名由来。G1将垃圾收集和内存整理活动专注于那些几乎全是垃圾的区域,并建立停顿预测模型来决定每次GC时回收哪些区域,以满足用户设定的停顿时间。

对于区域的回收通过复制算法实现。在完成标记清理后,G1将这几个区域的存活对象复制到一个单独区域中,实现内存整理和空间释放。这一过程通过多线程并行进行来降低停顿时间,提高吞吐量。通过这样的方式,G1在每次GC过程中持续清理碎片,控制停顿时间满足用户要求。这时过去虚拟机无法做到的。CMS不清理内存碎片(除非通过虚拟机参数设置,在每次或多次FullGC后进行整理),ParallelOld进行全堆整理,会导致较长的停顿时间。

G1不是实时垃圾收集器,它会尽量让停顿时间低于用户设置的停顿时间目标但不能保证一定如此。G1根据历史垃圾收集监测数据来 预测每个区域的回收时间,然后根据用户设定的目标停顿时间决定每次GC时可以回收哪些区域。G1通过这种方式建立比较精确的区域回收时间预测模型

G1垃圾收集器的模式

region

java -XX:G1HeapRegionSize=2M

1M、2M、4M、8M、16M、32M

young gc

如上图中,eden regions中没有足够空间时,触发young gc:将存活的对象复制到s regions或者old regions当中

mixed gc

当老年代old regions的大小占比达到一个阈值时,会触发mixed gc:回收所有年轻代regions和一部分老年代的regions。

full gc

当老年代的regions没有足够空间时,就会触发full gc:单线程回收老年代,暂停时间较长,需要尽量避免。

G1垃圾收集器的特点

  1. 可并行收集,充分利用cpu多核的资源
  2. 分代回收,不同代使用不同的回收算法
  3. 碎片整理(CMS不进行整理:标记-清除)
  4. 回收时间预测,预测每个region回收的时间,根据目标停顿时间对region进行选择性回收

ZGC垃圾回收

不论堆内存大小,都能保持低于10ms的停顿。把堆分成不同大小的region,回收时不进行分代。标记后,选择存活对象较少的region,将其中的存活对象复制到新的region中即可。

JVM堆内存与垃圾回收插图(3)
JVM堆内存与垃圾回收插图(4)
JVM堆内存与垃圾回收插图(5)

JVM垃圾收集器总结

Serial 串行收集器:单线程执行、停顿时间较长,一般用于client模式

java -XX:+UseSerialGC

Parallel 并行收集器:多线程执行、停顿时间短,一般用于server模式

java -XX:+UseParallelGC -XX:ParallelGCThreads=4

复制算法:效率较高,适合于频繁的收集,不过占用内存较大,需要两倍。所以多用于年轻代的回收

标记-整理算法:效率较低,整理内存碎片。一般用在老年代,因为老年代不需要频繁收集

标记-清除算法:效率较高,不整理碎片。CMS收集器中使用这种算法

CMS垃圾收集器:使用标记-清除算法,在多次Full GC后会进行一次整理。会预留一部分内存用于并发清理时用户程序分配使用,如果这部分不够用,则启用单线程串行进行收集,所以收集的时间无法预测,时间差异较大。

G1垃圾收集器:分成大小相等的region,优先收集垃圾较多的region。可预测每个region的收集时间。

java -XX:+UseG1GC

ZGC收集器:不论堆内存大小,都保持低于10ms的停顿,把堆分成不同大小的region,回收时不进行分代。

发表评论