一、对象死了吗
垃圾收集器在回收Java对象时,首先要确定对象是否存活。在虚拟机规范中,定义了两种方式判断对象的是否被回收。
1. 引用计数法(Reference Counting)
给对象添加一个引用计数器,当有地方引用到它时,计数器加1;当引用失效时,计数器减1;只要当计数器为0时,该对象就不可能再被引用。
引用计数器法的实现比较简单,判断效率也很高,但是在主流的Java虚拟机里面没有选用来管理内存,其中最主要的原因是它很难解决对象之间相互循环的引用问题。
/**
* Created by ZRD on 2017/02/14.
* 测试引用计数法的对象回收现象
*/
public class ReferenceCountingGC {
/**
* VM Args: -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime
* @param args
*/
public static void main(String[] args) {
testGC();
}
public Object instance = null;
private static final int _1MB = 1 * 1024 * 1024; //1B*1024=1KB
private byte[] bigSize = new byte[_1MB];
public static void testGC() {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
// 假设在这里发生GC
System.gc();
}
}
2017-02-14T10:27:19.994+0800: [GC [PSYoungGen: 4669K->2712K(37888K)] 4669K->2712K(123904K), 0.0217407 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]
2017-02-14T10:27:20.016+0800: [Full GC [PSYoungGen: 2712K->0K(37888K)] [ParOldGen: 0K->2577K(86016K)] 2712K->2577K(123904K) [PSPermGen: 2931K->2930K(21504K)], 0.0123248 secs] [Times: user=0.08 sys=0.00, real=0.01 secs]
2017-02-14T10:27:20.028+0800: Total time for which application threads were stopped: 0.0342405 seconds
Heap
PSYoungGen total 37888K, used 1638K [0x00000007d6000000, 0x00000007d8a00000, 0x0000000800000000)
eden space 32768K, 5% used [0x00000007d6000000,0x00000007d6199ac8,0x00000007d8000000)
from space 5120K, 0% used [0x00000007d8000000,0x00000007d8000000,0x00000007d8500000)
to space 5120K, 0% used [0x00000007d8500000,0x00000007d8500000,0x00000007d8a00000)
ParOldGen total 86016K, used 2577K [0x0000000782000000, 0x0000000787400000, 0x00000007d6000000)
object space 86016K, 2% used [0x0000000782000000,0x0000000782284580,0x0000000787400000)
PSPermGen total 21504K, used 2961K [0x000000077ce00000, 0x000000077e300000, 0x0000000782000000)
object space 21504K, 13% used [0x000000077ce00000,0x000000077d0e4608,0x000000077e300000)
4669K->2696K(37888K)上面的日志信息中可以获取到。说明这个时间段发生了垃圾回收,也就意味着虚拟机并没有因为这两个对象相互引用而没有回收,实际已经被回收了。
2. 可达性分析(Reachability Analysis)
可达性分析算法的思路是:以GC Roots为起点,从这些节点向下搜索,所有走过的路径称为引用链(Reference Chain);当一个对象没有GC Roots的对象引用它时,该对象就会成为垃圾收集器的目标
在Java语言中,GC Roots对象包括下面几种:
1. Java栈中引用的对象(也就是栈帧中的本地变量表)
2. 方法区中的类静态属性引用的对象
3. 方法区中的常量引用的对象
4. Native方法引用的对象
如上图,Object5,Object6,Object7会成为垃圾收集器的目标
二、reference引用
在Java中,有4中引用类型,用来定义引用的强弱。方便的内存不够时,逐步按等级定义回收的策略。
一些常见的缓存系统设计中比较常见
按引用的强弱程度分为4类
1. 强引用
强引用在代码中随处可见。类似ReferenceCountingGC objA = new ReferenceCountingGC();这种就是强引用。
只要强引用还存在,垃圾收集器永远不会回收。
2. 软引用(SoftReference)
- 描述一些还有用,但非必须的对象。
在系统发生内存溢出之前,将会把这些对象列入回收范围进行第二次回收。
如果回收还没足够的内存,则会发生内存溢出异常
3. 弱引用(WeakReference)
- 非必须的对象
- 只能存活到下次垃圾收集之前
当垃圾收集器开始工作时,无论内存是否足够,都会回收掉这类对象
4. 虚引用(PhantomReference)
- 幽灵引用或者幻影引用
一个对象是否有虚引用,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象实例。
- 设置该类引用的唯一目的是这个对象被垃圾收集器回收时能够收到一个系统通知
三、回收方法区
很多人认为方法区是没有垃圾收集的,Java虚拟机规范也说可以不要求在该区域实现垃圾收集,因为回收效果不好。
方法区的回收内容:废弃常量,无用的类
1. 回收废弃常量
回收废弃常量和回收Java堆中的对象非常相似,比如一个字符串,会放入常量池,当前系统中没有任何String对象引用常量池中的字符串,如果这个时候发生内存回收,系统会把字符串清理出常量池。
2. 回收无用的类
判断是否为无用的类有几个条件
* 该类的虽有对象均已被回收,也就是堆中不存在该类的任何对象
* 加载该类的ClassLoader已经被回收
* 该类对应的java.lang.Class对象没有任何地方被引用,也没有地方通过反射访问该类
对象的finalize()方法
当对象没有GC Roots引用链时,还没这么快被回收,对象会被第一次标记,并且还要做一个筛选判断有没有必要执行finalize(),筛选标准是对象没有重写finalize()或者已经被虚拟机调用过。
当判断有必要执行时,放入一个F-Queue队列中等待执行
如果在finalize()方法中将该对象重新产生一个GC Roots引用,则该对象成功逃过垃圾回收器的回收
但其实不鼓励这种做法