打卡学习JVM,第九天
本人学习过程中所整理的代码,源码地址
JVM垃圾回收(GC)模型
- 垃圾判断算法
- GC算法
- 垃圾回收器的实现和选择
- 垃圾判断算法
- 引用计数算法(Reference Counting)
- 给对象添加一个引用计数器,当有一个地方引用它,计数器加1,当引用失效,计数器减1,任何时刻计数器为0的对象就是不可能再被使用的
- 引用计数算法无法解决对象循环引用的问题
public class MyTest24 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getContextClassLoader());
System.out.println(Thread.class.getClassLoader());
}
}
- 根搜索算法(GC Roots Tracing)
- 在Java中使用此算法判断对象是否存活
- 算法基本思路是通过一系列的称为“GC Roots”的点作为起始进行向下搜索,当一个对象到GC Roots没有任何引用链(Reference Chain)相连,则证明此对象是不可用的
- 在Java语言中,GC Roots包括:在虚拟机栈(栈帧中的本地变量)中的引用,方法区中的静态引用,JNI(本地方法)中的引用
在方法区,主要回收两部分内容:废弃常量与无用类
其中类回收需要满足如下三个条件:
- 该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例
- 加载该类的ClassLoader已经被GC
- 该类对应的java.lang.Class对象没有在任何地方被引用,如不能在任何地方通过反射访问该类中的方法
JVM常见GC算法
- 标记-清除算法
- 标记-整理算法
- 复制算法
- 分代算法
- 标记-清除算法(Mark-Sweep)
- 算法分为“标记”和“清除”两个阶段,首先标记出所有需要回收的对象,然后回收所有需要回收的对象”
- 缺点:效率问题(需要扫描所有对象,堆越大,GC越慢),空间问题(标记清理之后会产生大量不连续的内存碎片,GC次数越多,碎片越严重,内存碎片太多可能导致后续使用中无法找到足够的连续内存而提前触发另一次的垃圾搜集工作)
- 标记-整理算法(Mark-Compact)
- 标记过程仍然一样,但后续步骤不是进行直接清理,而是令所有存活的对象向一端移动,然后直接清理掉这端边界以外的内存
- 优点:没有内存碎片
- 缺点:比标记-清除算法耗费更多的时间进行整理
- 复制搜集算法(Copying)
- 将可用内存划分为两块,每次只使用其中的一块,当半区内存用完,仅将还存活的对象复制到另一块上面,然后把原来整块内存空间一次性清理掉,这样使得每次内存回收都是对整个半区的回收,不需要考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效
- 优点:只需要扫描存活的对象,效率更高,不会产生碎片,非常适合生命周期比较短的对象,每次GC总能回收大部分对象,因此开销比较小
- 缺点:内存缩小为原来的一半,代价高昂,在对象存放率高的时候,效率有所下降,如果不想浪费50%的空间,就需要有额外的空间进行分配担保用于应付半区内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法
- 分代算法(Generational)
- 当前商用虚拟机的垃圾收集都是采用“分代收集”算法,根据对象不同的存活周期将内存划分为几块
- 一般是把Java堆分作新生代和老年代,根据各个年代的特点采用最适当的收集算法,比如新生代每次GC都有大批对象死去,只有少量存活,那就选用复制算法,而老年代一般对象存活周期较长,因此选用标记-整理算法或者标记-清除算法
- 优点:综合前面几种GC算法的优缺点,针对不同生命周期的对象采用不同的GC算法