1. 基础知识
1.1 什么是垃圾回收?
程序的运行必然需要申请内存资源,无效的对象资源如果不及时处理就会一直占有内存资源,最终将导致内存溢出,所以对内存资源的管理非常重要。
垃圾回收就是对这些无效资源的处理,是对内存资源的管理。
1.2 为什么要了解 GC?
在你排查内存溢出、内存泄漏等问题时,以及程序性能调优、解决并发场景下垃圾回收造成的性能瓶颈时,就需要对GC机制进行必要的监控和调节。
1.3 什么时候进行垃圾回收
会在cpu空闲的时候自动进行回收在堆内存存储满了之后
主动调用 System.gc() 后尝试进行回收
补充:System.gc() 用于调用垃圾收集器,在调用时,垃圾收集器将运行以回收未使用的内存空间。它将尝试释放被丢弃对象占用的内存。 然而System.gc()调用附带一个免责声明,无法保证对垃圾收集器的调用。所以 System.gc() 并不能说是完美主动进了垃圾回收。
1.4 GC 发生在 JVM 哪部分?
在回答这个问题之前,有必要先了解 JVM 的体系结构图:
对于 JVM 各部分的解读,这里不在做过多说明,具体可以参考文章:Java 虚拟机结构。
GC主要发生在堆中,堆区由所有线程共享,在虚拟机启动时创建。堆区主要用于存放对象实例及数组,所有new出来的对象都存储在该区域。
JVM 虚拟栈,本地方法栈,程序计数器不需要进行垃圾回收,因为他们的生命周期是和线程同步的,随着线程的销毁,他们占用的内存会自动释放。所以,只有方法区和堆区需要进行垃圾回收,回收的对象就是那些不存在任何引用的对象。
2. 如何判断对象已死(或能够被回收)
既然名叫垃圾回收,那么哪些对象成为“垃圾”呢?已经不再被使用的对象便视为“已死”,就应该被回收。在Java中,GC只针对于堆内存,Java语言中不存在指针说法,而是叫引用,在堆内存中没有被任何栈内存引用的对象应该被回收。
2.1 引用记数法
既然没有引用就可以回收,引用计数法应运而生。简单的来说就是判断对象的引用数量。
实现方式:给对象共添加一个引用计数器,每当有引用对他进行引用时,计数器的值就加1,当引用失效,也就是不在执行此对象,它的计数器的值随之减1,若某一个对象的计数器的值为0,那么表示这个对象没有被其他对象引用,也就是意味着是一个失效的垃圾对象,就会被GC进行回收。
缺点:无法解决对象减互相循环引用的问题。即当两个对象循环引用时,引用计数器都为1,当对象周期结束后应该被回收却无法回收,造成内存泄漏。
2.2 可达性分析算法
目前主流使用的都是可达性分析算法来判断对象是否存活。算法基本思路:以“GC Roots”作为对象的起点,从此节点开始向下搜索,搜索所走过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
哪些对象可作为GC Roots?
虚拟机栈(栈帧中的本地变量表)中引用的对象;
方法区中类静态属性引用的对象;
方法区中常量引用的对象;
本地方法栈中JNI(Native方法)引用的对象;
活跃线程的引用对象。
在可达性分析法中,判定一个对象 objA 是否可回收,至少要经历两次标记过程:如果对象 objA 到 GC Roots没有引用链,则进行第一次标记。
如果对象 objA 重写了 finalize() 方法,且还未执行过,那么 objA 会被插入到 F-Queue 队列中,由一个虚拟机自动创建的、低优先级的 Finalizer 线程触发其 finalize() 方法。finalize() 方法是对象逃脱死亡的最后机会,GC 会对队列中的对象进行第二次标记,如果 objA 在 finalize() 方法中与引用链上的任何一个对象建立联系,那么在第二次标记时,objA 会被移出“即将回收”集合。
下面给个示例代码,具体结论,大家可以自己验证。
@Overrideprotected void finalize() throwsThrowable {super.finalize();
System.out.println("method finalize is running");
object= this;
}public static void main(String[] args) throwsException {
object= newFinalizerTest();//第一次执行,finalize方法会自救