JVM 垃圾回收理论部分
- 在学习垃圾回收之前,需要了解JVM 运行时数据区。
- JVM堆和垃圾回收。
运行时数据区例子
说明对象创建时的消耗
public void newObject() {
Object obj = new Object();
}
- 生成了2部分的内存区域,
- obj这个引用变量,因为是方法内的变量,放到JVM Stack里面。
- 真正Object.class的实例对象,放到Heap里面。
- 上述的new语句一共消耗12个bytes,JVM规定引用占4个bytes(在JVM Stack),而空对象是8个bytes(在Heap)。
- 方法结束后,对应Stack中的变量马上回收,但是Heap中的对象要等到GC来回收。
JVM 垃圾回收(Garbage Collection)模型
垃圾回收需要分步骤解决一些问题
- 垃圾判断算法,判断哪些内容是可以回收的。
- 引用计数算法(Referenc Counting)
- 根搜索算法(Root Tracing)
- GC算法,收集垃圾的算法。
- 标记-清除算法(Mark-Sweep)
- 标记-整理算法(Mark-Compact)
- 复制算法(Copying)
- 分代算法(Generational)
- 垃圾回收器的实现和选择,GC算法是内存回收的方法论,垃圾收集器就是算法的落地实现。
- Serial 垃圾收集器
- ParNew 垃圾收集器
- Parallel Scavenge 收集器
- Serial Old 收集器
- Parallel Old 收集
- CMS 收集器(重点)
- G1 收集器(重点)
关于方法区的垃圾收集
方法区垃圾回收
- Java虚拟机规范表示可以不要求虚拟机在这区实现GC,这区GC的“性价比”一般比较低
- 在堆中,尤其是在新生代,常规应用进行一次GC一般可以回收70%~95%的空间,而方法区的GC效率远小于此
- 当前的商业JVM都有实现方法区的GC(HotSpot有实现),要回收两部分内容:
- 废弃常量与无用类
- 主要回收两部分内容:废弃常量与无用类类回收需要满足如下3个条件
- 该类所有的实例都已经被GC,也就是JVM中存在该Class的任何实例
- 加载该类的ClassLoader已经被GC
- 该类对应的java.lang.Class对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法
- 在大量使用反射、动态代理、CGLib等字节码框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要JVM具备类卸载的支持以保证方法区不会溢出。
垃圾判断算法
引用计数算法(Reference Counting)
- 给对象添加一个引用计数器,当有一个地方引用它,计数器加1,当引用失效,计数器减1。
- 任何时刻计数器为0的对象就是不可能再被使用的。
- 引用计数算法无法解决对象循环引用的问题。
根搜索算法(GC Roots Tracing)
- 在实际的生产语言中(Java、C#等),都是使用根搜索算法判定对象是否存活。
- 算法基本思路就是通过一系列的称为"Roots"的点作为起始进行向下搜索,当一个对象到GC Roots没有任何引用链(Reference Chain)相连,则证明此对象是不可用的。
- 在Java语言中,GC Roots包括在VM栈(帧中的本地变量)中的引用,方法区中的静态引用。
- JNI(即一般说的Native方法)中的引用。
那如果堆中的对象互相应用?且没有GC Roots指向它们,它们就一定是垃圾吗?GC Roots算法一定不会导致误杀吗?
- 如果在堆中存在一些对象,它们互相引用且没有任何GC Roots指向它们,那么这些对象将被判定为不可达的垃圾对象。但是,GC Roots算法并不会导致误杀,因为它是基于“引用可达性分析”的原理来判断对象是否可达的。也就是说,如果一个对象不是从GC Roots开始可达的,那么它就会被判定为不可达的垃圾对象,然后被回收掉。但是,如果这个对象确实有被使用的价值,那么它就应该被持有或者加入到其他引用链中,这样它就不会被误判为垃圾对象了。