JVM-垃圾回收算法

1.8之前版本 java内存模型
在这里插入图片描述
1.8 版本java内存模型
在这里插入图片描述
元空间替换永久代是位了规避永久代内存溢出的问题 ,metaSpace 可以动态无限扩容。无限扩容可能会占用过多内存而使得其他区域内存不足所以。实际应用中仍然需要设置上限大小。

垃圾回收的对象的确定
引用计数:
在对象中添加一个引用计数 器,当有地方引用这个对象的时候,计数器+1,当失效的时候,计数器-1.会存在相互引用而导致内存泄露的情况,所以java没有使用。
可达性分析:
判断从GCRoot可以达到的对象就是不可回收的对象,否则可以回收。
作为GCRoot的对象
• 虚拟机栈局部变量表中引用的对象
• 本地方法栈所引用的对象
• 方法区的类属性所引用的对象
• 方法区的常量所引用的对象

不可达是否一定会被回收?
如果类被回收前有必要执行finalize()方法,GC在回收前会调用该方法,如果该方法让自己重新可达,那对象不会被回收,否则会在下一次垃圾回收时被回收。当对象没有覆盖finalize()方法或finalize()方法已经被虚拟机掉用过,都是没有必要执行。
引用类型
jdk1.2之后java对引用的概念进行了扩充,将引用强度由强到弱分为
强引用-- Object obj=new Object(); 永远不会回收
软引用–描述一些有用但非必须的对象,在系统将要内存溢出前,回收
弱引用–也是用来描述非必须对象的,被弱引用关联的对象只能生存到下一次垃圾收集发生之前
虚引用-- 也称为幽灵引用或幻影引用,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例,设置虚引用关联的唯一目的就是能在对象被垃圾收集时收到一个系统通知

回收方法区
方法区垃圾收集效率比较低。
永久代垃圾收集主要回收两部分内容:
废弃常量
常量池中没有被程序中引用到的常量。类、方法、字段等也类似
无用类
满足以下三个条件
该类所有的实例都已经被垃圾回收,java堆中不存在该类的实例
加载该类的ClassLoader已经被回收
该类对应的java.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法

虚拟机可以对满足上述三个条件的类进行回收,不是和对象一样不适用了必然会回收。是否回收 hotspot 虚拟机提供了-Xnoclassgc参数控制,还可以使用-verbose:class 以及-XX+traceClassLoading、-XX:+traceClassUnLoading查看类加载和卸载信息 其中-verbose.class 和-XX:+traceClassLoading可以再product版虚拟机中使用,-XX:+TraceClassUnLoading 参数需要fastDebug版虚拟机支持

垃圾收集算法

标记-清除算法
算法分为标记和清除两个阶段。首先标记处所有需要回收的对象,在标记完成后统一回收被标记的对象。
不足:效率低,标记和清理两个过程效率都不高。会产生大量不连续的内存碎片。碎片太多容易导致程序需要为较大对象分配内存时无法找到足够连续的内存而不得不进行一次垃圾收集动作。
复制算法
为了解决效率问题。一种称为复制的算法出现了它把内存分为大小相等的两块,每次只使用其中一块当这一块的内存用完了就讲还存活的对象复制到另外一块上面然后把已使用过的内存空间一次清理掉。
这样每次对半区内存进行回收,也不用考虑内存碎片的问题。但是内存缩小为原来的一半代价太高。

现在商业虚拟机都采用这种垃圾算法来回收新生代。只不过比例不是1:1.而是将内存分为一块较大的Eden和两块较小的survivor,每次使用eden和其中一块survivor 。当回收时将eden和survivor中还存活的对象一次性的复制到另外一块survivor空间上,最后清理掉eden和用过surviro的空间。hotspot默认eden和survivor大小比例是8:1 ,也就是每次新生代可用内存容量为 原来的90% 10%内存被浪费。当然98%的对象可回收只是一般场景下的数据,不能保证每次回收都只有不多于10%的对象存活,当survivor空间不够用时需要依赖其他内存(老年代)进行分配担保。

标记-整理算法
复制手机算法在对象存活率较高时就会进行较多的复制操作,效率变低。更关键的是如果不想浪费50%的空间就必须要有额外的空间进行担保分配。以应对所有对象都存活的极端情况。所以老年代不能直接使用这种算法。
根据老年代的特点有人提出 标记整理 算法。
标记过程和标记-清除算法一样,但事后续的步骤不是直接对可回收对象进行清理而是让所有存活的对象都向一端移动,然后清理掉端边界以外的内存。

分代收集算法
当前商业虚拟机的垃圾收集都采用分代收集 算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块,一般是把java堆分成新生代和老年代,这样可以根据各个年代的特点采用最适当的收集算法。新生代选用复制法,老年代存活率高没有额外空间对它进行分配担保,就必须使用标记清理或标记整理算法进行回收。

猜你喜欢

转载自blog.csdn.net/zhangxm_qz/article/details/88575759