深入理解Java虚拟机—垃圾收集器与内存分配策略
在Java的运行时数据区的各个部分,其中程序计数器、虚拟机栈、本地方法栈都为线程私有,随着线程生灭。因此垃圾收集器的主要作用区域是线程共享的堆和方法区。
确定哪些对象需要被回收?
引用计数算法
算法思路:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器的值就加一;当引用失效时,计数器的值就减一;任何时刻计数器为零的对象就是不会再被使用的。
引用计数法执行起来很简单,效率也比较高,但是有一个很难解决的问题就是循环引用。什么是循环引用?
对象 objA和对象objB都由字段 instance,赋值令 objA.instance = objB.instance,除次之外,这两个对象再无其它引用,此时就会产生循环引用的问题。
但是测试过程中发现虽然代码中存在循环引用的问题,最终还是进行了垃圾回收,也就是说Java虚拟机并不是通过引用计数算法来判断对象是否是存活的。
可达性分析法
算法思路:通过一系列被称为 “GC Roots”的根对象作为起始结点集,从这些结点开始,根据引用关系向下搜索,搜索过程所走过的路径称为 “引用链”,如果某个对象到GC Roots间没有任何引用链相连。或者用图论的话来说就是从 GC Roots 到这个对象不可达时,则证明此对象是不可能再被使用的。
其中固定可以作为GC Roots
的对象包括以下几种:
- 虚拟机栈中引用的对象,比如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
- 在方法区中类静态属性引用的对象,比如Java引用类型的静态变量;
- 在方法区中类的静态常量引用的对象,比如说字符串常量池里面的引用;
- 在本地方法栈中JNI(Native方法)引用的对象;
- Java虚拟机内部的引用,比如说基本数据类型对应的Class对象,一些常驻的异常对象等,还有系统类加载器。
- 所有被同步锁(Synchronized)持有的对象。
再谈引用
在JDK1.2 版本之后,Java对引用的概念进行了扩充,将引用分为强引用(Strongly-Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)四种,四纵强度依次减弱。
- 强引用:通常指
Object obj = new Object()
这种引用关系,无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。 - 软引用:指一些有用但非必须的对象,只被软引用关联的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
- 弱引用:指一些非必须的对象,强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集开始工作时,无论当前对象内存是否足够都会回收掉只被弱引用关联的对象。在JDK1.2版本之后提供了Weak Reference 类来实现弱引用。
- 虚引用:虚引用也被称为幽灵引用或者幻影引用,是最弱的一种引用关系。无法通过虚引用来获取一个对象实例,为一个对象设置虚引用的唯一目的就是能在这个对象被收集器回收时收到一个系统通知,可以通过
PhantomReference
类来实现虚引用。
判断对象生存还是死亡
如果对象在可达性分析法中被标记为不可达状态,此时它不会直接被回收,而是需要经过第二次标记后才能确定,也就是说一个对象的回收至少需要经过两次标记过程。
对象如果通过可达性分析法后发现没有与GC Roots相连接的引用链,那么它会第一次被标记,然后进行一次筛选,筛选的条件是该对象是否有必要执行 finalize()
方法。如果说对象没有覆盖finalize()
或者对象的finalize()
方法已经被虚拟机调用过,那么会被视为没必要执行,结果就是回收该对象。
如果对象有必要去执行finalize()
方法,那么该对象会被放在一个F-Queue
队列之中,并在稍后由一条虚拟机自动建立的、低优先级的Finalizer
线程去执行它们的finalize()
方法。对象如果想要逃脱被回收的命运,就必须重新与引用链上的任何一个对象建立关联,比如说把自己(this关键字) 赋值给某个类变量或者对象的成员变量