如何判断对象可以回收
用两种算法判断:引用计数法,可达性分析算法
引用计数法
引用计数法:对象实时被引用的次数,引用次数为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,而非整个堆

年轻代回收执行步骤

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





混合回收执行步骤





总结
垃圾回收主要针对堆


1328

被折叠的 条评论
为什么被折叠?



