JVM垃圾回收

如何判断对象可以回收

用两种算法判断:引用计数法,可达性分析算法

引用计数法

引用计数法:对象实时被引用的次数,引用次数为0,代表可被回收。

循环引用可能出现问题。各自引用计数都是1。导致AB对象都不会被删

可达性分析算法

Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象

根对象指肯定不能被当成垃圾的对象

算法原理:先扫描堆中所有对象,看看对象是不是被根对象直接或间接的引用,如果是那就不能被回收,vice versa。
扫描堆中的对象,看是否能够沿着 GC Root对象 为起点的引用链找到该对象,找不到,表示可以
回收

哪些对象可以作为 GC Root ?(根对象)

利用Memory Analyzer工具,jmap(查看堆内存占用情况,抓取内存快照,再用Memory Analyzer分析快照)

命令:jamp -dump:format=b,live,file=filename.bin 进程ID(利用jps查看)

转储文件格式       抓快照时只抓存活的,还会触发一次垃圾回收               文件存储的位置

Memory Analyzer打开刚才生成的文件,javabasics--GCroots就可以查看

四种引用

1. 强引。用被GC Roots 对象的引用就是强引用

只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收

只要被强引用,该对象就不会被回收

2. 软引用(SoftReference)

仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用
对象。可以配合引用队列来释放软引用自身

垃圾回收后,内存还不够,那就回收被软引用的对象

3. 弱引用(WeakReference)

仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
可以配合引用队列来释放弱引用自身

4. 虚引用(PhantomReference)

必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,
由 Reference Handler 线程调用虚引用相关方法释放直接内存

5. 终结器引用(FinalReference)

无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象
暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize
方法,第二次 GC 时才能回收被引用对象

引用队列

被软/弱引用的对象被回收后,软/弱引用本身也是一个对象(也占用内存),如果开始有引用队列,那就会被放到引用队列里。利用引用队列找到软/弱引用本身,然后释放

垃圾回收算法

垃圾回收算法要做的事:找到内存中存活的对象(利用可达性分析算法找到存活的对象)给存活和不存活的对象打上相应标记。释放不存活对象的内存

常见的垃圾回收算法(4种)

Java垃圾回收过程会通过单独的GC线程来完成,但是不管使用哪一种GC算法,都会有部分阶段需要停止所有的用户线程。这个过程被称之为Stop The World简称STW,如果STW时间过长则会影响用户的使用。

垃圾回收算法的评价标准

吞吐量=执行用户代码时间/(执行用户代码时间+GC时间)。吞吐量越高越好

最大暂停时间:就是STW时间,最大暂停时间越短越好

堆使用效率:

标记清除算法

两个阶段

阶段1:标记,利用可达性分析算法,标记存活的对象

阶段2:开始清理没有标记的对象

优缺点

优点:实现简单,只需要维护标志位即可

缺点碎片化问题,内存有碎片,该算法不会整理内存空间,会造成有很多内存碎片,如果后续要加载较大内存的对象,那就加载不进来。分配速度慢,给对象分配内存时,需要遍历链表,链表记录每个内存碎片的大小,需要一直遍历才能找到对应位置

复制算法

复制算法的核心思想是:
1.准备两块空间From空间和To空间,每次在对象分配阶段,只能使用其中一块空间(From空间)

2.在垃圾回收GC阶段,将From中存活对象 复制到To空间。

3.直接清理From空间,再将两块空间的From和To名字互换。

优缺点

优点:吞吐量高,没有碎片化问题

缺点:内存利用率太低。性能较标记清除算法低,因为有对象复制的过程

标记整理算法

阶段1:可达性分析标记存活的对象

阶段2:整理内存,将存活的对象移动到内存的一段,另一端包含的就是不再使用的对象,直接清理另一端即可

优缺点

优点:没有内存碎片,内存使用率高

缺点:整理阶段效率不高

分代GC/分代垃圾回收算法

将内存划分为:年轻区(存放存活时间较短的对象)和老年区(存放存活时间较长的对象)

年轻区又有伊甸园区两个幸存者区S0,S1

调整内存区域的大小:

算法逻辑

分代回收时,创建出来的对象,首先会被放入Eden伊甸园区。

随着对象在Eden区越来越多,如果Eden区满,新创建的对象已经无法放入,就会触发年轻代的GC,称为Minor GC或者Young GC。

Minor GC会根据可达性分析算法,把需要eden中和From(S0)需要回收的对象回收,把没有回收的对象放入To区(使用了复制算法)。

接下来,S0会变成To区,S1变成From区。当eden区满时再往里放入对象,依然会发生MinorGC。
此时会回收eden区和S1(from)中的对象,并把eden和from区中剩余的对象放入S0。
注意:每次Minor GC中都会为对象记录他的年龄,初始值为0,每次GC完加1

如果Minor GC后对象的年龄达到阈值(最大15,默认值和垃圾回收器有关),对象就会被晋升至老年代。

当老年代中空间不足,无法放入新的对象时,先尝试minor GC。如果还是不足,就会触发Full GC,Full GC会对整个堆进行垃圾回收。

如果Full GC依然无法回收掉老年代的对象,那么当对象继续放入老年代时,就会抛出Out Of Memory异常。

为什么要分成老年代和年轻代

垃圾回收器

垃圾回收器就是算法的实现类

serial

清理垃圾的线程是串行的,我清理的时候,用户线程都停了,严重影响用户体验

parallel

清理垃圾时是并行的,比串行稍快,并线还是避免不要用户线程的停顿

CMS

浮动垃圾:在并发清理时,可能同时有对象不用了,要等到下次清理才能清理掉

G1垃圾回收器

jdk9以后强烈建议使用G1垃圾处理器

G1垃圾处理器内存结构

不再是把整个堆分为:伊甸园,幸存区,老年代。

Humongous区域​​:专门存放超大对象(超过Region大小50%的对象)

无需回收整个堆,而是选择一个Collection Set(CS)

G1垃圾回收的两种方式:全年轻代回收、混合回收(全年轻代老年代都回收)

估计每个region中的垃圾比例,优先回收垃圾多的region

但是存在问题:老年代对象可能持有年轻代的引用。不同region相互引用

如何解决

引出了:CardTable和RememberedSet

CardTable:解决老年代引用年轻代的问题

将堆内存划分为512字节的"卡"(Card)

维护一个卡表(字节数组),每个卡对应卡表中的一个字节

当老年代对象修改引用(写入字段)时,JVM会将对应卡标记为"脏"(写屏障)

回收时的应用​​:

年轻代GC时只需扫描卡表中标记为"脏"的卡对应的内存区域
避免扫描整个老年代

RememberedSet:解决跨Region引用问题,可以记录谁引用了我。

每个Region维护自己的Remembered Set(简称RSet)
记录其他Region中指向本Region的引用
使用"精化"(Refinement)线程处理写屏障记录

数据结构​​:

通常采用哈希表或位图实现
存储形式为:(外部Region地址 → [内部引用指针列表])

回收时的应用​​:

当回收某个Region时,通过其RSet可以知道哪些其他Region包含对本Region的引用
只需扫描这些特定Region,而非整个堆

年轻代回收执行步骤

使用了复制算法,不会产生内存碎片

混合回收执行步骤

总结

垃圾回收主要针对

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值