GC 即 Garbage Collection,中文 意思“垃圾回收”,在有GC以前,咱们手动去管理内存,若是你忘记标记某一处已经再也不使用的内存,那么这块内存将永远不会被系统回收,也就是常说的 “内存泄露”。java
如下全部的 GC 介绍,所有基于主流 JVM 虚拟机 Hotspot。数组
GC判断一个对象存活或死亡就是判断这个对象还存不存在它的引用,常见的两种方式以下安全
每一个对象从建立开始,都会维护一个引用计数器,每当引用一次,那么计数器增长1,引用失效一次,那么计数器减去1,这样实现优势是高效、简单,可是缺点也很明显:没法解决循环依赖,好比下面的代码,虽然 A引用B,B引用A,可是就没有其余地方引用了,所以它们是无效引用,形成内存泄露。Java 天然不会选择这种方式做为判断方式。bash
A=B
B=A
复制代码
将一系列的 GC Roots 对象做为起点,开始向下搜索。可做为 GC Root 的起点有测试
Java 虚拟机栈(栈桢本地变量表中)引用的对象spa
本地方法栈中JNI(也就是常说的 Native 方法)线程
方法中的常量、类静态属性引用的对象翻译
注意:向下搜索的路径就是引用链code
为了方便理解,我画了下面的图片cdn
特别注意: 可达性分析仅仅是判断对象是否可达,但还不足以判断对象是否存活或者死亡。可达性分析中判断为不可达的对象,只是被判刑 ≠ 死亡。
不可达对象会存放在 「即将回收」集合中,要判断一个对象是否真正的死亡,还须要通过下面的两个步骤。
可达性分析中标记为不可达的对象,会经历第一次筛选。
筛选标准:判断对象是否须要执行 finalize() 方法,如有必要执行,则筛选进行下一阶段分析,若不必执行,那么该对象断定为死亡,不筛选,等待系统回收。
当对象无 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过一次的状况下,那么被标记为不必执行,等待回收。
当对象通过了第一次筛选后没有被回收,将进行第二次筛选。
该对象会被放在一个 F-Queue
的队列中,并由虚拟机自动建立一个名为 Finalizer 的低优先级的线程去执行队列中全部对象的 finalize 方法,这里须要注意的是,finalize 方法只会被执行一次,而且 JVM 并不承诺 finalize 会执行完毕,缘由是为了防止 finalize 执行时间过长或者中止执行,致使的内存溢出。
筛选标准: 在执行 finalize 方法的过程当中,若是该对象依旧没有和 GC Root 关联起来,那么该对象被判断为死亡,留在即将回收集合,等待回收。
简单理解就是,全局GC(Full GC)和局部GC(Partial GC),分别看一下:
Young GC :只收集 Young Gen(年轻代)的 GC, Young GC 还有一个叫法 叫 Minor GC。
old GC : 只收集 Old Gen(年老代) 的GC 只有垃圾回收器 CMS 的 concament colletton 有这个模式。
mixed GC : 收集整个Young GC的GC和部分的old Gen的GC,只有垃圾回收器 G1 有这个模式。
Full GC是对整个堆来讲的,执行Full GC 的时候会回收全部代,包括永久代、年老代、年轻代等等全部的 GC。Full GC 的触发条件有如下几种
此方法的调用是建议JVM进行Full GC,虽然只是建议而非必定,但不少状况下它会触发 Full GC,从而增长Full GC的频率,也即增长了间歇性停顿的次数。强烈建议能不使用此方法就别使用,让虚拟机本身去管理它的内存,可经过经过-XX:+ DisableExplicitGC来禁止RMI(Java远程方法调用)调用System.gc。
旧生代空间只有在新生代对象转入及建立为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出以下错误: java.lang.OutOfMemoryError: Java heap space 为避免以上两种情况引发的FullGC,调优时应尽可能作到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要建立过大的对象及数组。
JVM规范中运行时数据区域中的方法区,在HotSpot虚拟机中又被习惯称为永生代或者永生区,Permanet Generation中存放的为一些class的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的状况下也会执行Full GC。若是通过Full GC仍然回收不了,那么JVM会抛出以下错误信息: java.lang.OutOfMemoryError: PermGen space 为避免Perm Gen占满形成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。
若是发现统计数听说以前Minor GC的平均晋升大小比目前old gen剩余的空间大,则不会触发Minor GC而是转为触发full GC
为了方便理解上述枯燥又无味的理论,我写了几行代码,实验了一下
/** * GCRoots 测试:虚拟机栈(栈帧中的局部变量)中引用的对象做为GCRoots * -Xms1024m -Xmx1024m -Xmn512m -XX:+PrintGCDetails * <p> * 扩展:虚拟机栈中存放了编译器可知的八种基本数据类型,对象引用,returnAddress类型(指向了一条字节码指令的地址) * * @author wangjie */
public class TestGCRoots01 {
private int _10MB = 10 * 1024 * 1024;
private byte[] memory = new byte[8 * _10MB];
public static void main(String[] args) {
method01();
System.out.println("返回main方法");
System.gc();
System.out.println("第二次GC完成");
}
public static void method01() {
TestGCRoots01 t = new TestGCRoots01();
System.gc();
System.out.println("第一次GC完成");
}
}
复制代码
当运行上述代码控制台会输出以下内容
首先该类声明了一个 80M 的数组
private byte[] memory = new byte[8 * _10MB];
复制代码
而后调用了 method01,method01建立了一个TestGCRoots01的实例,该实例存放在 PSYoungGen 也就是年轻代
method01();
TestGCRoots01 t = new TestGCRoots01();
复制代码
这时调用了 System.gc(),该实例从PSYoungGen到了ParOldGen也就是年老代
System.gc();
System.out.println("第一次GC完成");
复制代码
可是并无被回收,method01执行完毕后返回到了 main 方法,这时又执行了一次 System.gc(),该实例被回收 。
System.out.println("返回main方法");
System.gc();
System.out.println("第二次GC完成");
复制代码
至此,GC 的基础知识你应该了解了,可是这篇文章仅仅简单分析了一下 GC 和 JVM 的关系,并不涉及到引用链,若是对你理解 GC 有帮助,点赞转发是对我最大的支持。
另外,我为你找到了如下资料,并翻译成了中文供你查阅