深入理解JVM(三)--对象存活判定算法
GC的历史比Java的历史要久远的多。虽然目前内存的动态分配与内存回收技术已经相当成熟,但是当需要排查各种内存溢出、内存泄漏的问题以及当垃圾回收系统成为系统达到更高并发的瓶颈时,我们就需要实施必要的监控和调节了。
java中内存区域中的程序计数器、虚拟机栈、本地方法栈3个区域随着线程生灭,因此,这三个区域内就不需要过多考虑回收的问题。
但是JVM堆和方法区则不同。比如说一个接口的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也不一样,我们只有在程序运行期间才能知道会创建那些对象,这部分的内存分配都是动态的。垃圾回收器关注的是这部分内存。接下来主要说明Java回收机制对堆内存和方法区的回收。
堆内存回收
引用:
Java1.2以前,Java的引用定义非常传统,引用类型中的数值代表着另一块内存的地址,就称为这块内存代表着一个引用。这种定义太过狭隘,对于一些鸡肋的对象就显得无能为力。我们希望能描述这样的对象:当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾回收之后还是非常紧张,则可以抛弃的。很多系统的缓存功能都符合这样的场景 。
Java1.2后,Java对引用进行了扩充,分别为:强引用、软引用、弱引用、虚引用。
强引用:强引用在代码中普遍存在,例如
Object obj=new Object()
这类的引用。只要强引用存在,垃圾回收器永远不会回收掉被引用的对象。软引用:**软引用来描述一些还有用但并非必须的对象,在系统要发生内存溢出之前,将会把这些对象列入回收范围之中进行第二次回收,如果第二次回收还没有足够的内存,才会抛出内存溢出异常。**Java1.2之后,提供了SoftReference类来实现软引用。
弱引用:弱引用也是用来描述必须对象的,但是它的强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾回收发生之前。当垃圾回收器工作时,无论内存是否足够,都回收掉只被弱引用关联的对象。Java1.2后,提供了WeakReference来实现弱引用。
虚引用:它是最弱的一种引用关系。它无法通过虚引用来取得一个对象实例。唯一目的就是能在这个对象被回收之前会收到一个系统通知。Java1.2之后,提供了PhantomReference类来实现虚引用。
引用计数算法
引用计数算法在大部分情况下都是不错的算法,它也有一些著名的案例:微软公司的COM技术、人生苦短,我用Python的Python等中都使用了引用计数算法进行内存管理。但是,至少主流的JVM中没有选用这种算法。因为它很难解决对象之间相互循环引用的问题。
举个简单的例子:
public class Test {
public Object instance=null;
private static final int _1MB=1024*1024;
private byte[] bigSize=new byte[2*_1MB];
public static void testGC(){
Test objA=new Test();
Test objB=new Test();
objA.instance=objB;
objB.instance=objA;
objA=null;
objB=null;
System.gc();
}
public static void main(String[] args) {
testGC();
}
}
上面的代码中对象objA和对象objB都有字段instance,赋值 objA.instance=objB;objB.instance=objA; 除此之外两个对象再无任何引用。其实这两个对象已经不可能再被访问,但是它们互相又引用这对方,若使用引用技术算法,它们的引用计数器都不为零 ,这就导致引用计数器无法通知GC来回收它们。
但是,上述代码执行后,观察日志可以发现,JVM并没有因为这俩对象相互引用就不回收它们,这也从侧面反映了JVM并不是采用引用计数算法来判断的。
可达性分析算法
在JVM中,是通过 可达性分析算法来判断对象是否存活。此算法的基本思想是通过一系列的称为“GC Roots”的对象作为起始点。从这些节点向下开始搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链向链的时候,则证明此对象是不可用的。
上述图中,object1、object2、object3、object4通过引用链都与GC Root有关联。所以这四个对象称为对象可达。但是,object5、object6、object7虽然他们相互具有引用关系,但是与GC Root 却没有关系。所以他们称为对象不可达。
在JVM中,可以作为GC Root的对象包括:
- 虚拟机栈中引用的对象。
- 方法区中静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中Native方法引用的对象。
方法区内存回收
JVM中在方法区中进行垃圾收集的“性价比”低。因为在堆中进行一次垃圾回收机制一般可以回收75%-95%的对象。方法区的垃圾回收主要回收两部分:废弃常量和无用的类。假如一个字符串abc已经在常量池中,若没有任何String来引用这个字符串常量,这个abc就会被清理出常量池,常量池中的其他接口、方法、字段的符号引用也与此类似。判定类是否是废弃常量。需要以下三个条件:
- 该类所有的实例都已经被回收,Java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收。
- 该类对应的Class对象没有任何地方被引用,无法在任何地方通过反射访问该类。
是否对类进行回收,HotSpot虚拟机也提供了许多参数进行控制。可以自行搜索。