JVMJVM03(图解垃圾回收机制)下

Posted 温文艾尔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVMJVM03(图解垃圾回收机制)下相关的知识,希望对你有一定的参考价值。

⭐️写在前面


  • 这里是温文艾尔の学习之路
  • 👍如果对你有帮助,给博主一个免费的点赞以示鼓励把QAQ
  • 👋博客主页🎉 温文艾尔の学习小屋
  • ⭐️更多文章👨‍🎓请关注温文艾尔主页📝
  • 🍅文章发布日期:2021.12.29
  • 👋java学习之路!
  • 欢迎各位🔎点赞👍评论收藏⭐️
  • 🎄新年快乐朋友们🎄
  • 👋jvm学习之路!
  • ⭐️上一篇内容:【JVM】JVM03(图解垃圾回收机制)上

文章笔记参考黑马程序员

文章目录


⭐️1.垃圾回收器

⭐️1.1串行

底层是一个单线程的垃圾回收器,在垃圾回收发生时,其他的线程都暂停
  • 单线程
  • 堆内存较小,适合个人电脑

开启串行垃圾回收器设置:

-XX:+UseSerialGC = Serial + SerialOld

Serial收集器:工作在新生代,采用复制算法
SerialOld收集器:工作在老生代,采用标记+整理算法


回收过程

在线程运行的过程中,堆内存紧张,触发垃圾回收,为了避免因对象地址发生改变引发的错误,更安全的使用对象,需要将这些线程在一个安全点停下来,此时完成垃圾回收的工作就不会被其他线程干扰,因为Serial和SerialOld都是单线程的垃圾回收器,在垃圾回收线程运行的同时,其他线程要阻塞

⭐️1.2吞吐量优先

  • 多线程
  • 堆内存较大,多核cpu
  • 单位时间内,STW的时间最短cpu 0.2 0.2 0.2 = 0.6

开启吞吐量有限的垃圾回收器

// 这两个开关在1.8默认开启 //吞吐量优先垃圾回收器开关,第一个是新生代垃圾回收器,采用复制算法,第二个
是老年代的垃圾回收器,采用标记+整理算法,只要开启其中一个,另一个也会连带着被开启
-XX:+UseParallelGC~-XX:+UseParallelOldGC
// 2.采用自适应的大小调整策略,动态调整新生代(伊甸园 + 幸存区FROM、TO)的比例,包括整个堆的大小
-XX:+UseAdaptiveSizePolicy  
// 3.调整吞吐量的目标,吞吐量 = 垃圾回收时间/程序运行总时间(1/(1+radio))
-XX:GCTimeRatio=ratio
// 4.垃圾收集最大停顿毫秒数,默认值是200ms
-XX:MaxGCPaiseMillis=ms
// 5.控制ParallelGC运行的线程数
-XX:ParallelGCThreads=n

和上面串行垃圾回收器不同的是,在达到安全点之后会开启多个线程,垃圾回收线程的个数和cpu核数有关

⭐️1.3响应时间优先

专注于响应时间优先的老年代的垃圾回收器

  • 多线程

// UseConMarkSweepGC:基于标记清除算法的垃圾回收器,工作在老年代,支持并发(用户线程和垃圾回收线程
可以并发执行),UseParNewGC:工作在新生代,基于复制算法
-XX:+UseConMarkSweepGC~-XX:+UseParNewGC~SerialOld  
// ParallelGCThreads=n并行的垃圾回收线程数,一般与cpu核数一致
// ConcGCThreads=threads并发GC线程数,threads值最好围并行线程数的1/4
-XX:ParallelGCThreads=n~-XX:ConcGCThreads=threads
// 执行CMS垃圾回收的内存占比:预留一些空间给浮动垃圾用,在早期的jvm中,它的默认值是65%,值越小cms触发垃圾回收的时机就越早
-XX:CMSInitiatingOccupancyFraction=percent
// 重新标记之前,对新生代进行垃圾回收
-XX:+CMSScavengeBeforeRemark

CMS收集器:
全称:ConcurrentMarkSweep

cms垃圾回收器有时会发生并发失败的问题,这是它会采取补救措施,让老年代的的垃圾回收器,从cms并发垃圾回收器退化到SerialOld单线程垃圾回收器

垃圾回收过程

  • 首先cpu开始并行执行,随后老年代发生内存不足

  • 当这些线程都到达了安全点,这时cms垃圾回收器会执行一个初始标记的动作

  • 在执行初始标记时,会触发stop the world,其他的用户线程阻塞

  • 等待初始标记完成以后,用户线程恢复运行,垃圾回收线程继续并发标记,把剩余的垃圾找出来,和用户线程并发执行,此过程响应时间很短

  • 并发标记以后,进行重新标记,为了避免并发标记工作时用户线程也在工作,使现有对象发生变化,或是产生一些新的引用,对垃圾回收造成干扰,进行stop the world

  • 重新标记以后,用户线程恢复运行,垃圾回收线程进行并发清理,对标记对象进行回收

注意,在初始标记和重新标记阶段会造成STW(stop the world),其他时间都是并发执行的

⭐️1.4G1

全称:
Garbage First

时间线

  • 2004论文发布
  • 2009JDK 6u14体验
  • 2012JDK 7u4官方支持
  • 2017JDK9默认

适用场景

  • 同时注重吞吐量(Throughput)低延迟(Low latency),默认的暂停时间是200 ms
  • 超大堆内存,会将堆划分为多个大小相等的Region区域
  • 整体上是标记+整理算法,两个区域之间是复制算法
// G1垃圾回收器的开关
-XX:+UseG1GC
// 所划分的Region区域内存大小:1 2 4 8 16
-XX:G1HeapRegionSize=size
// 垃圾回收最大停顿时间
-XX:MaxGCPauseMillis=time

⭐️1.4.1垃圾回收阶段


回收的三个阶段

  • Young Collection:对新生代的垃圾收集,当老年代的内存超过阈值,进入下一阶段
    - Young Collection+Concurrent Mark:对新生代的垃圾收集,同时执行一些并发标记,进入下一阶段
  • Mixed Collection:对新生代,幸存区,老年代进行规模较大的收集
  • 收集结束后,再次进入新生代的垃圾收集

⭐️1.4.1.1 Young Collection工作流程

新生代内存布局

  • 会STW
  • E:伊甸园
  • S:幸存区
  • O:老年代

当区域被占满,就会触发一次新生代的垃圾回收


将伊甸园中幸存的对象E以复制的算法,放入幸存区S

当幸存区的对象S较多,或者存活寿命超过阈值,此时又会触发新生代垃圾回收,幸存区的对象S有一部分会晋升到老年代O

⭐️1.4.1.2 Young Collection+Concurrent Mark

  • 在Young GC时会进行GC Root的初始标记
  • 老年代占用堆空间比例达到阈值时,进行并发标记(不会STW),由下面的JVM参数决定
-XX:InitiatingHeapOccupancyPercent=percent // 默认值45%

⭐️1.4.1.3 Mixed Collection

会对E(伊甸园),S(幸存区),O(老年代)进行全面垃圾回收

  • 最终标记(Remark)会STW
  • 拷贝存活(Evacuation)会STW
//  用于指定GC最长的停顿时间
-XX:MaxGCPauseMillis=ms


混合收集阶段工作

伊甸园区(E)中的幸存对象会被复制算法复制到幸存区,另一些幸存区中的没有达到阈值的对象也会被复制算法复制到幸存区,另一些符合晋升条件的对象会被晋升到老年代区域
还有一些老年代中的无用的对象,复制算法会将幸存对象复制到新的老年代区域

为什么有的老年代被复制算法复制而有的没有呢?
设置最大暂停时间后,G1垃圾回收器会根据最大暂停时间去有选择的回收老年代中的有用对象,这样做也是为了避免耗时超出最大暂停时间。所有优先收集垃圾最多的区域

⭐️1.4.1.4 Full GC

  • Serial GC
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足发生的垃圾收集 - full gc
  • ParallelGC
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足发生的垃圾收集 - full gc
  • CMS
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足
  • G1
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足,根据阈值,达到对内存的45%时,触发并发标记以及混合收集阶段,如果回收速度比新产生垃圾速度要快,此时处于并发垃圾收集阶段,如果回收速度比新产生垃圾速度慢,就触发full gc

⭐️1.4.2 Young Collection跨代引用

新生代回收的跨带引用(老年代引用新生代)问题

如果新生代对象的根对象有一部分来自于老年代,老年代的存活对象很多,如果去遍历整个老年代,去找根对象,效率会很低,因此我们采用卡表(card Table),把老年代区域再进行细分,分成一个个card,每一个card为512B,如果老年代其中有一个对象引用了新生代对象,那么对应的card就被标为“脏card”


这样我们遍历只需要关注脏card而不是整个老年代,这样减少搜索范围,提高扫描效率

我们以粉红色的区域为脏卡区域

  • 卡表与Remembered Set

  • 在引用变更时通过post-write barrier + dirty card queue

  • concurrent refinement threads 更新Remembered Set

⭐️1.4.3Remark重标记

下图表示并发标记阶段时对象的处理状态,

  • 黑色表示已经处理完成,并且有引用,在结束时会被保留下来
  • 灰色为正在处理当中
  • 白色为未处理对象



假如我们此时处理到了白色C对象,但是此时引用断了,C和B之间没有联系了

等待整个并发标记结束之后,C对象因为无引用,就会被当成垃圾回收掉

如果C被处理完标记为垃圾对象之后,并发标记还未结束,而用户线程改变了C的引用地址,A强引用了C


此时C已经被标记为垃圾对象,并且A已经处理完毕,所以不应该被回收的C就会被垃圾回收

为了避免此类问题的发生,我们要对对象的引用做进一步的检查,即Remark(重标记)阶段

进入重标记阶段

当对象的引用发生改变时,jvm就会对对象加入写屏障

写屏障指令会把C加入到队列当中,并且将C标记为正在处理中(灰色)状态

等到整个并发标记结束,进入重标记阶段,其他线程阻塞,队列中的对象会一个一个被检查,如果是灰色就做进一步的判断处理,这样C对象就不会被当成垃圾回收

以上是关于JVMJVM03(图解垃圾回收机制)下的主要内容,如果未能解决你的问题,请参考以下文章

图解JVM垃圾内存回收算法

java什么时候进行垃圾回收,垃圾回收的执行流程

图解 Java 垃圾回收机制

JVM虚拟机-03JVM内存分配机制与垃圾回收算法

JVMJVM系列之垃圾回收

80.Android之内存管理