垃圾收集器与内存分配策略之--对象已死吗
GC关注的问题其实就是三个:
- 哪些内存可以被回收
- 何时回收
- 如何回收
所有的GC问题都是关于这三点的描述。
对于JVM来说,首先线程独有的三块(程序计数器、栈、本地方法栈)都是随着线程的开始而创建,线程结束而消亡,一个栈帧在开始时内存已经固定,所以GC没必要处理这部分内存。我们所说的GC都是针对堆和方法区而言的。
对象已死吗
如何判断一个对象已死?一般主流两种方法:引用计数法和可达性分析法
引用计数法
很简单,每个对象维护一个引用计数器,当某个引用指向这个对象时,就让这个引用计数器加1。当一个对象的引用计数器为0,说明对象已死。
致命的缺点:
不好解决循环引用问题。
可达性分析法
通过一组“GC Root”的对象作为起始点,向下搜索,走过的路径上的每个对象是还活着的。不可达的对象就是死的。HotSpot用的就是这个方法。
Java中可被作为GC Root的对象有四种:
- 栈中引用指向的对象
- 方法区中static引用指向的对象
- 方法区中常量引用指向的对象
- 本地方法栈中引用指向的对象
再谈引用
一般我们说的引用都是指强引用。
Object obj = new Object();//obj就是一个强引用
而Java中引入一些“弱”的引用。目的为了引入一种这样的对象:当内存足够时,对象保留内存中,当内存不够时,这些对象可被回收。
Java里引用分为四种:强引用、软引用、弱引用、虚引用。引用强度逐渐减弱。
这四种引用的区别就是指向的对象的生存时间不一样。
- 强引用
这个就是我们普通说的引用,如果一个对象存在这种引用。即时内存要溢出,这些对象也不能被回收。 - 软引用
在要发生内存溢出之前,会先将这些引用指向的对象回收调。(其中这种说法不对,应该是一个对象只有软引用指向它时)。
比如我们定义一个软引用:
SoftReference<String> s = new SoftReference<String>(new String("123"));
- 弱引用
生命周期为下一次GC之前。无论内存是否足够,这些指向的对象都会被回收。
WeakReference<String> w = new WeakReference<String>(new String("234"));
- 虚引用
一个对象是否有虚引用和它的生命周期毫无关系。虚引用的唯一目的就是在对象被回收的时候会受到一个系统通知。
ReferenceQueue<String> referenceQueue = new ReferenceQueue<String>();
PhantomReference<String> p = new PhantomReference<>(new String("434"), referenceQueue);//当对象被回收时,referenceQueue就会受到一个消息
一般软引用和弱引用都用于本地缓存中,每次用时判断一下是否被回收,如果被回收再去到数据库里捞。
SoftReference<String> s = new SoftReference<String>(new String("123"));
if (null == s.get()) {//如果已被回收
//到数据库里捞
}else {//如果没被回收
System.out.println(s.get());//直接使用
}
生存还是死亡
一个对象GC Root不可达并不代表着立刻死亡。一个对象的死亡要经历两次标记。
具体过程如下:
对象死亡过程
- 当GC Root不可达之后被标记一次,
- 然后判断这个对象是否需要执行finalize方法。
判断不需要的标准是:对象没有重写finalize方法或finalize已被虚拟机执行过。[也就是说对象finalize只能被执行一次] - 如果判断需要执行,则将这个对象丢到F-Queue中,然后虚拟机会有一个Finalize线程去消费执行对象的finalize方法,但并不会等待执行结束。如果在执行finalize方法中[在被标记第二次之前]又把此对象关联到GC Root上,这个对象就又会活过来。【注意这种现象一个对象只能出现一次,因为finalize方法只可能会执行一次】
- 所以说一个对象的finalize被执行了,这个对象可能还是活着的。且finalize方法被强烈建议不要使用,这个方法不会有人保证它是否会执行、何时执行、何时执行结束。
回收方法区
回收方法区的性价比很低,且Java虚拟机规范中并没有强制要求要回收方法区。
回头方法区主要回收两点:常量和无用的类。
- 常量
这里说的就是常量池中常量,比如字面量和符号引用。
判断是否应该回收比较简单,比如字面量:系统中没人叫这个东西了,自然就可以被回收了。 - 无用的类
这个判断起来就比较麻烦了。
主要由三点:
该类的实例已全部被回收
加载该类的ClassLoader已被回收
该类的Class对象无引用且无法使用反射来调用
满足这三点是可以被回收,但也不是一定就会被回收。可以使用-Xnoclassgc来控制。
网友评论