手记

死磕JVM-垃圾收集器机制

Java GC主要可以由三个问题展开来讲

1、哪些内存需要回收

2、什么时候回收

3、怎样回收

这三个问题讲清楚后,至少在脑海里对Java GC有了一个比较清晰的理解,那接下来我们就一个个攻克这三个问题。

1、哪些内存需要回收

就Java内存运行时区域来说,线程私有的程序计数器、虚拟机栈和本地方法栈随线程生而生,随线程亡而亡,因此这几个区域的内存分配和回收具备确定性,也就不需要过多考虑回收的问题,因为方法体结束或线程结束时,内存自然就跟着回收了。我们需要考虑的是线程共享的方法区和Java堆。

2、什么时候回收

下图是这一节的简介图

“什么时候回收”这个问题对于Java堆的直接的回答就是当对象死亡的时候,那对象什么死亡或者如何来标记一个对象是死是活呢?Java GC中有两种标志算法:引用计数算法和可达性分析算法。

2.1 引用计数算法

算法思想:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1,当引用失效时,计数器值减1。但触发一次GC的时候,对象的计数器的值为0的对象就说明该对象不可以再使用,可以被回收。

这种标记的算法很简单高效,但就像《死磕JavaScript-垃圾收集机制》中引用计数算法存在同样的弊端:相互循环引用。简单说就是对象A保有对象B的引用,同时对象B也保有对象A的引用,那么这两个对象的引用计数在任何时候都不会为0,也就是不会被GC回收。

2.2 可达性分析算法

算法思想:通过一系列的“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径为引用链,如果一个对象到每个GC Roots都不存在引用链,也即是说从GC Roots到这个对象不可达,那么就说明这个对象是死亡的,可以被GC回收的对象。

在Java语言中,可以被当做GC Roots的对象有:

1)虚拟机栈的本地变量表中的引用对象

2)本地方法栈中引用的对象

3)方法区中类静态属性引用对象

4)方法区中常量引用对象

2.3 引用的分类

在Java1.2之前,对象只有被引用和未被引用两种状态,对象在未被引用的情况下就会在下一次触发GC的时候被GC回收,但我们有些对象当前可能没有被引用,但它可能经常被引用,那么在这个时刻由于它没有被引用则被GC回收了,在下次被重新引用的时候又需要加载进来,多次这样的操作后效率肯定是不高的,那么为了避免这种情况,在Java1.2之后,我们将引用分为4种。

2.3.1 强引用

这是普遍存在的一种引用,比如“Object obj = new Object()”,只要强引用存在,GC就不会回收这类引用的对象。

2.3.2 软引用 SoftReference

这是一种有用但非必需的对象,只有当内存不够用的时候,在抛出内存溢出之前才会将它列入GC回收的范围中(也就是说如果内存够用,这种引用的对象不会被列入GC回收的范围),只有在GC回收掉这些引用对象后内存还是不够用的情况下才会抛出内存溢出异常。

2.3.3 弱引用 WeakReference

弱引用关联的对象只能生存到下一次GC之前,当GC工作时,不管内存是否够用都会回收掉这部分对象

2.3.4 虚引用 PhantomReference

这是最弱的一种引用关系,我们无法通过虚引用得到一个对象实例,为一个对象设置虚引用关联的唯一目的是在这个对象被GC的时候收到一个系统通知。

上面讲的是Java堆的对象的回收,现在来讲下方法区中的回收。

方法区(永久代)中的回收主要有两部分内容:废弃常量和无用的类。废弃常量指的是没有任何对象引用了这个常量;无用的类必需满足以下三个条件:

1)这个类的所有实例都被回收

2)该类的ClassLoader也被回收

3)该类对应的java.lang.Class对象没有被引用,即无法通过反射来访问这个类的方法

3、怎样回收

3人推荐
随时随地看视频
慕课网APP