JVM垃圾回收总结

1. 如何判断对象可以回收?

1.1 引用计数法

给对象添加一个计时器,
只要对象被其他对象引用,让记数+1; 若变量不再引用了,让记数-1 ;当引用为0则回收

缺点:当循环引用时会造成内存泄漏 !
在这里插入图片描述

A和B的引用记数都是1,而没有其他对象引用A或B,导致记数无法归零,造成内存泄露------------早期python虚拟机使用的引用计数法,而java用的是可达性分析法;

1.2 可达性分析法

【在垃圾回收前】,会对堆中的所有对象扫描,找出是否有对象被GC root根对象引用,如果是则不能被回收,反之一个对象没有被根对象直接/间接引用,则可以被回收。

哪些是GC root根对象
在这里插入图片描述
1.系统类的对象
2. 本地方法栈引用的对象
3. 线程中虚拟机栈 栈帧所引用的对象
4. Monitor对象即正在加锁的对象
( GC root根对象指的是堆中的对象实体,而不是引用 )

1.3 强、软、弱、虚 引用

对于一个对象来说,只要有强引用的存在,它就会一直存在于内存中
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它; 如果内存空间不足,就会回收这些对象的内存
一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的空间
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

平时用的所有引用都是强引用,即传统的引用,如new 对象把对象赋给引用变量,则这个变量强引用了刚刚的对象;除非当根对象不再引用时会被回收。当对象被强引用时,处于可达状态不可能被回收,即使以后都不会用到,所以强引用是造成内存泄漏的主要原因
软引用用来描述一些还有用,但非必须的对象,
弱引用也是用来描述那些非必须的对象,但是强度比软引用更弱一些,弱引用关联的对象只能生存到下一次GC手收集发生为止
虚引用是最弱的一种引用关系,无法通过虚引用来取得对象实例
如:配合bytebuffer使用,给一个对象设置虚引用关联的目的在于能在这个对象被GC回收时收到一个系统通知

2. 垃圾回收算法

2.1 标记-清除

使用可达性分析法,沿着GCroot的引用链去找,扫描整个堆中对象,如果对象被GCroot根对象引用,则保留
①将未被根对象引用的对象标记;
②清除;
在这里插入图片描述
优点:速度快,只需要将内存的起始地址做一个记录就完成了
缺点:容易产生内存碎片;清除后不会再对空闲的空间进行整理工作了,即空闲的空间不连续,当有大的新对象的时候,放不下!造成内存溢出;

2.2 标记-整理

第一个阶段和标记-清除中一样
①使用可达性分析法,将未被根对象引用的对象标记;
②通过紧凑技术解决标记-清除算法的内存碎片问题,通过将可用的对象向前移动,让内存更为紧凑,连续的空间更多;
在这里插入图片描述
优点:没有内存碎片
缺点:整理涉及到了对象的移动,对象移动涉及到引用地址的改变效率较低
标记-整理算法中对象可能会移动多次,而标记-复制算法中只需要移动一次。

2.3 标记-复制

①使用可达性分析法,在FROM区域,将未被GC root根对象引用的对象标记
②将FROM区域存活的对象,复制到TO区域,复制过程中会完成内存碎片的整理,复制完成后,FROM区域全是垃圾,
③清除FROM区域的所有垃圾
④交换FROMTO区域
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
优点:不会产生内存碎片,只需要移动一次对象,而标记-整理算法是移动多次
缺点:占用双倍的内存空间

3. 分代垃圾回收机制(完全stw)

仅适用于单线程Serial、并行Parallel 垃圾回收器, 而并发的CMS、G1在老年代的机制不同!

在这里插入图片描述

堆内存被划分为两块:新生代老年代
新生代划为:Eden伊甸园 survior幸存区(From + to);

新生代:一般保存新出现的对象,通常在每次垃圾收集时都会有大批对象死去,需要频繁的去回收,只有少量对象存活,便采用了 标记-复制算法,只需要移动少量存活对象即复制开销小,就可以完成收集;

老年代中一般保存长时间存活的对象,他们存活率高、没有额外空间对它进行分配担保,就必须采用 标记-整理算法

永久代就是JDK1.7之前的方法区。在这里都是放着一些被虚拟机加载的类信息,静态变量,常量等数据。这个区中的东西比老年代和新生代更不容易回收。

老年代的垃圾回收很久才进行一次,新生代的垃圾回收更频繁;

工作过程

在这里插入图片描述

  1. 当建立一个新的对象时,先放到新生代的Eden区域;
  2. Eden伊甸园空间不足,就触发一次 Minor GC 垃圾回收,同时stw,会暂停所有的用户线程;然后使用可达性分析算法,沿GC root根对象的引用链标记存活对象,然后使用标记-复制算法,把【Eden和幸存区From区域】存活的对象复制到幸存区的TO区域,并将TO区域幸存的对象寿命+1,然后清除【伊甸园Eden和幸存区From】的对象,最后交换FROM和TO区域;
  3. 当【幸存区】的对象寿命达到阀值(最大15次),就将该对象放入老年代晋升机制),说明是生存时间比较长、相对比较重要的对象;
    当老年代区内存不足,会先尝试触发Minor GC,同时stw,如果空间仍不足,会触发 Full GC ,使用标记-整理算法,标记未被GC root引用的对象,将可用的对象向前移动,再清除回收对象,保证老年代有连续的内存空间,如果老年代空间依然不足,则OutOfMemoryError 内存溢出;

注意:minor gc / full gc会引发 stop the world ,会暂停其他的用户线程,由gc线程完成垃圾回收,其他的用户线程才能继续运行
原因: minor gc运行时,对象的内存地址会发生改变,如果多个线程同时运行,会造成混乱!等gc结束之后,用户线程恢复运行
新生代大部分都是垃圾,存活率低,需要复制到幸存区TO区域的对象少,所以stop the world很快就能完成;
老年代存活率高,stop the world需要的时间更长;

为什么新生代要分Eden和两个 Survivor 区域?
①Survivor的预筛选保证,就是减少被送到老年代的对象(老年代回收频率低),进而减少Full GC(进行一次Full GC消耗的时间比Minor GC长得多)
②使用标记-复制算法,解决了内存碎片问题

空间:
在这里插入图片描述

4. 垃圾回收器

4.1 串行 Serial

在这里插入图片描述
串行垃圾回收器分为两部分:Serial + SerialOld;
使用分代垃圾回收机制
serial工作在新生代,采用标记-复制算法
serialOld工作在老年代,使用标记-整理算法;
过程:(完全stop the world)
在这里插入图片描述
当发现内存不够了,触发gc,让线程在安全点停下 (stop the world)即阻塞,然后让gc线程(单线程),等待gc结束,其他线程再运行。

4.2 吞吐量优先 —Parallel并行

在这里插入图片描述
使用分代垃圾回收机制
也是完全 stop the world,并开启多个线程进行回收;
在这里插入图片描述

4.3 响应时间优先 — CMS / G1

4.3.1 CMS垃圾回收器

CMS的全称:Concurrent Mark Sweep,即「并发标记清除」;

引入:
如果使用Serial 单线程和Parallel并行垃圾回收器,在垃圾回收时,用户线程会完全stop the world;
为了缩短老年代STW的时间,使用并发垃圾回收器。CMS能够在「部分场景」下让GC线程和用户线程并发执行

优点:使用多核CPU来缩短STW的时间

缺点

  1. 由于【老年代】使用 标记-清除算法 ,会产生内存碎片,分配对象时内存不足,造成并发失败触发FullGC,这时老年代并发垃圾回收器会退化到SerialOld串行垃圾回收器 使用 标记-整理 算法并进行整理,整理至碎片减少,才能继续工作,但这会导致垃圾回收的整体时间变长;
  2. 空间需要预留:CMS垃圾收集器可以一边回收垃圾,一边处理用户线程,那需要在这个过程中保证有充足的内存空间供用户使用。(碎片内存又加剧了空间的问题,导致出发FullGC而使卡顿时间可能更长);
  3. STW 的时间不可预估--------G1引入;

运行过程:
【当新生代内存不足时】
Minor GC,并不属于CMS的机制 !

  1. 当建立一个新的对象时,先放到新生代的Eden区域,
  2. 当Eden伊甸园空间不足,就触发一次 Minor GC 垃圾回收,同时用户线程STW,使用可达性分析算法,沿GC root根对象的引用链标记存活对象,然后使用 标记-复制 算法,把存活的对象从Eden和FROM区域复制到幸存区的TO区域,并将幸存的对象寿命+1,然后清除Eden和FROM区域的对象,最后交换FROM和TO区域;
  3. 当幸存区的对象寿命达到阀值(最大15次),就将该对象放入老年代(晋升机制)

【当老年代内存不足时】
在这里插入图片描述

  1. 初始标记:当老年代发生了内存不足,进行初始标记,用户线程STW,CMS会标记GCRoot直接关联的对象,这个阶段的速度算是很快的,因为没有「向下追溯」(只标记一层)
  2. 并发标记:【用户线程并发运行】,根据标记为存活的对象向下追溯遍历,找出所有存活的对象;
    ②通过写屏障将老年代中引用发生变化的对象标记为 Dirty Card 脏卡(提高重新标记时的效率,不需要扫描整个老年代 )
  3. 重新标记:用户线程STW,由于在【并发标记时】,由于用户线程也在工作,对象的引用可能变化,需要重新标记,遍历GCRoot和Dirty Card;
  4. 并发清理:用户线程恢复并发运行,清理未使用的对象并回收;

卡表:
在逻辑上将老年代分割为若干个Card,每个Card准备一个标记索引,HotSpot中每个卡占512kb;

引入1:CMS的并发标记
在CMS的【并发标记】时,用户线程和GC并发执行,可能产生新的对象、或者对象引用发生变化,
如: 新生代晋升到老年代、大对象直接分配在老年代、老年代的引用变化等
这些情况需要重新标记,为了提高重新标记的效率,在【并发标记】阶段会把这些变化对象的Card标记为脏卡,这样后续就只需要扫描这些Dirty Card对象而不需要扫描整个老年代

引入2:G1的跨代引用
新生代回收的跨代引用(老年代引用新生代)问题
而老年代存对象多,不可能全部去扫描,则使用 Card Table技术来避免全表扫描老年代的对象,将老年代分成一个个Card
因为Minor GC 是回收新生代的对象,但如果老年代有对象引用着新生代代,那这些被老年代引用的对象也不能回收掉
如果老年代中的对象引用了新生代的对象,则对应的卡被标记为脏卡,对应新生代RemenberedSet记忆集(本质是个HashTbale)会记录老年代的脏卡的索引,避免遍历整个老年代去查找新生代的引用,提高效率;

将来在对新生代进行垃圾回收时,可以先通过RenmeneredSet知道对应的脏卡,再到老年代的脏卡区域去遍历GC Root,这样减少了GC Root遍历的时间

脏卡通过post-write barrier 写屏障来实现,在每次对象引用发生变更时,都要去更新维护脏卡,此为异步操作,会将更新脏卡的指令放到 dirty card queue 队列中

4.3.2 G1垃圾回收器

引入:
G1可以理解为在CMS垃圾收集器上进行”升级”, CMS垃圾回收器STW停顿时间是不可预估的, 而G1垃圾收集器可以可以设定一个STW停顿时间,然后根据设定的STW的时间来进行回收;

在这里插入图片描述
重新定义了堆空间,打破了原有的分代模型
将堆内存[逻辑]划分为【多个大小相等的】Region区域 (为了更好控制回收的时间),每个区域独立作为伊甸园、幸存区、老年代、大对象区… 这样就能对每个Region 收集的成本进行量化,使得STW的时间更容易控制;
划分为小的区域就可以化整为零,目的在于 加快标记和复制的速度(当堆内存特别大时,g1的优势就很明显)

整体采用标记-整理 算法,两个区域之间用标记-复制算法;

优点:
G1最大的特点就在于 【可预测的】停顿时间模型----- 软实时 ,即用户可以指定垃圾回收时间的限时,G1会努力在这个时限内完成垃圾回收

缺点:
G1无论是为了垃圾收集产生的内存占用还是程序运行时的额外执行负载都要比CMS要高。

大对象:
H它代表Humongous,即超过region一半大的对象
大对象占内存大,复制成本高,所以不会对大对象进行拷贝,
回收时优先考虑;
一旦发现没有引用指向大对象,就可直接在年轻代的Minor GC中被回收掉;

为什么要将「堆空间」进行「细分」多个小的区域 ?
堆空间(内存)大的时候,每次进行「垃圾回收」都需要对一整块大的区域进行回收,那收集的时间是不易控制的。而划分为小区域,使垃圾回收时的STW更容易控制。

收集器为什么能建立可预测的停顿时间模型 ?
将堆内存逻辑上分为多个Region区域,G1 会通过一个合理的计算模型,将每个 Region收集的成本 进行 量化,这样一来,收集器在给定了STW时间限制的情况下,总是能选择一组恰当的 Region 作为收集目标,让其收集开销满足这个限制条件。

运行过程:
【当新生代内存不足时】:

  1. 【当Eden区域满了】,触发 Minor GC ,用户线程STW,标记GCRoot直接关联的对象
  2. 更新记忆集:MinorGC是回收新生代对象,当有老年代引用新生代时,这些被引用的对象不能被回收—跨代引用
    在G1中,每个Region区域都有rememberSet记忆集,是个HashTbale哈希表, 如果当前Region被老年代对象引用,那么记忆集的key存Region的起始地址,value记录其他Card Table的索引集合(新生代的记忆集保存老年代的索引,而老年代的记忆集保存其他老年代的索引),然后将这些记忆集中的对象引用做标记,以免被回收
  3. 使用 标记-复制 算法,将存活的对象复制到空的幸存区、寿命达到阀值的晋升到老年代;
  4. 处理引用队列,软引用,弱引用,虚引用;

【当老年代内存不足时】:
【当老年代占用堆空间达到阀值】(默认45%,可以调整)会触发 MixedGC

  1. 初始标记:只标记一下GC Roots能直接关联到的对象,用户线程STW老年代新生代都会扫(Mixed);
  2. 并发标记:用户线程并发运行,根据标记为存活的对象向下追溯遍历,找出所有存活的对象;
  3. 最终标记:是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间引用变化的对象记录在线程 新生代的Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set记忆集 中,这阶段需要停顿线程,但是可并行执行。
  4. 筛选回收:用户线程STW,会根据设定的停顿时间,由「停顿预测模型」来决定回收哪些Region;
    即一次回收未必是将所有的垃圾进行回收的,G1会依据停顿时间来选择回收Region的数量;

Full GC】:
当没有多余Region可以分配,无法Mixed GC,造成并发失败触发FullGC,这时老年代并发垃圾回收器会退化到SerialOld 串行垃圾回收器使用 标记-整理算法进行清楚;

CMS一样,在并发失败后,会退回SerialGC串行垃圾回收— FullGC ;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JVM中的老年代垃圾回收是指对老年代进行垃圾回收的过程。老年代是堆内存的一部分,用于存放生命周期较长的对象。老年代垃圾回收的触发条件与空间分配担保有关。在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。如果大于,则此次Minor GC是安全的;如果小于,则虚拟机会根据HandlePromotionFailure设置值来决定是否进行空间分配担保。 空间分配担保是指在发生Minor GC时,如果老年代的连续空间小于新生代所有对象的总空间,虚拟机会根据HandlePromotionFailure的设置值来决定后续的操作。如果HandlePromotionFailure为true,虚拟机会进一步检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小。如果大于,则尝试进行一次Minor GC,但仍然有风险;如果小于或者HandlePromotionFailure为false,则会进行一次Full GC。 总结一下,老年代垃圾回收是在发生Minor GC时对老年代进行的垃圾回收过程。空间分配担保的目的是确保老年代有足够的空间来容纳新生代的对象。如果空间不足,根据HandlePromotionFailure的设置值,虚拟机会决定是继续进行Minor GC还是进行一次Full GC。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [JVM垃圾回收——对象进入老年代](https://blog.csdn.net/weixin_39555954/article/details/130042958)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值