⭐️写在前面
这里是温文艾尔の学习之路
- 如果对你有帮助,给博主一个免费的点赞以示鼓励把QAQ
- 博客主页 温文艾尔の学习小屋
- ⭐️更多文章请关注温文艾尔主页
- 文章发布日期:2021.12.29
- java学习之路!
- 欢迎各位点赞评论收藏⭐️
- 新年快乐朋友们
- jvm学习之路!
- ⭐️上一篇内容:【JVM】JVM03(图解垃圾回收机制)上
文章笔记参考黑马程序员
底层是一个单线程的垃圾回收器,在垃圾回收发生时,其他的线程都暂停
开启串行垃圾回收器设置:
-XX:+UseSerialGC = Serial + SerialOld
Serial收集器:工作在新生代,采用复制算法
SerialOld收集器:工作在老生代,采用标记+整理算法
在线程运行的过程中,堆内存紧张,触发垃圾回收,为了避免因对象地址发生改变引发的错误,更安全的使用对象,需要将这些线程在一个安全点停下来,此时完成垃圾回收的工作就不会被其他线程干扰,因为Serial和SerialOld都是单线程的垃圾回收器,在垃圾回收线程运行的同时,其他线程要阻塞
// 这两个开关在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核数有关
专注于响应时间优先的老年代的垃圾回收器
// 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),其他时间都是并发执行的
全称:
Garbage First
时间线
适用场景
吞吐量(Throughput)
和低延迟(Low latency)
,默认的暂停时间是200 ms
Region
区域标记+整理算法
,两个区域之间是复制算法
// G1垃圾回收器的开关
-XX:+UseG1GC
// 所划分的Region区域内存大小:1 2 4 8 16
-XX:G1HeapRegionSize=size
// 垃圾回收最大停顿时间
-XX:MaxGCPauseMillis=time
阈值
,进入下一阶段并发标记
,进入下一阶段新生代内存布局
当区域被占满,就会触发一次新生代的垃圾回收
将伊甸园中幸存的对象E以复制的算法,放入幸存区S
当幸存区的对象S较多,或者存活寿命超过阈值,此时又会触发新生代垃圾回收,幸存区的对象S有一部分会晋升到老年代O
-XX:InitiatingHeapOccupancyPercent=percent // 默认值45%
会对E(伊甸园),S(幸存区),O(老年代)进行全面垃圾回收
// 用于指定GC最长的停顿时间
-XX:MaxGCPauseMillis=ms
伊甸园区(E)中的幸存对象会被复制算法复制到幸存区,另一些幸存区中的没有达到阈值的对象也会被复制算法复制到幸存区,另一些符合晋升条件的对象会被晋升到老年代区域
还有一些老年代中的无用的对象,复制算法会将幸存对象复制到新的老年代区域
为什么有的老年代被复制算法复制而有的没有呢?
设置最大暂停时间后,G1垃圾回收器会根据最大暂停时间去有选择的回收老年代中的有用对象,这样做也是为了避免耗时超出最大暂停时间。所有优先收集垃圾最多的区域
新生代回收的跨带引用(老年代引用新生代)问题
如果新生代对象的根对象有一部分来自于老年代,老年代的存活对象很多,如果去遍历整个老年代,去找根对象,效率会很低,因此我们采用卡表(card Table),把老年代区域再进行细分,分成一个个card,每一个card为512B,如果老年代其中有一个对象引用了新生代对象,那么对应的card就被标为“脏card”
这样我们遍历只需要关注脏card而不是整个老年代,这样减少搜索范围,提高扫描效率
我们以粉红色的区域为脏卡区域
卡表与Remembered Set
在引用变更时通过post-write barrier + dirty card queue
concurrent refinement threads 更新Remembered Set
下图表示并发标记阶段时对象的处理状态,
假如我们此时处理到了白色C对象,但是此时引用断了,C和B之间没有联系了
等待整个并发标记结束之后,C对象因为无引用,就会被当成垃圾回收掉
如果C被处理完标记为垃圾对象之后,并发标记还未结束,而用户线程改变了C的引用地址,A强引用了C
此时C已经被标记为垃圾对象,并且A已经处理完毕,所以不应该被回收的C就会被垃圾回收
为了避免此类问题的发生,我们要对对象的引用做进一步的检查,即Remark(重标记)阶段
进入重标记阶段
当对象的引用发生改变时,jvm就会对对象加入写屏障
写屏障指令会把C加入到队列当中,并且将C标记为正在处理中(灰色)状态
等到整个并发标记结束,进入重标记阶段,其他线程阻塞,队列中的对象会一个一个被检查,如果是灰色就做进一步的判断处理,这样C对象就不会被当成垃圾回收