JVM 第三章 垃圾收集器与内存分配策略 (一)
3.1 概述
程序计数器,虚拟机栈,本地方法3个区域随线程而生,随线程而灭;栈中的Stack Frame随着方法进入和退出有条不紊地进行着出栈入栈操作。每个Stack Frame中分配多少内存基本上是在类结构确定下来时已知的,因此这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟着回收了。
而Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器所关注的是这部分内存。
3.2 对象已死吗
在堆里面存放着几乎Java中的所有对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象哪些还“存活着”,哪些已经“死去”(即不可能再被任何途径使用的对象)。
3.2.1 可达性分析算法
可达性算法是用来判定对象是否存活的。这个算法的最基本思路是通过一系列的被称为“GC Roots”的对象座位起始点,从这些节点开始向下搜索,所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明次对象是不可引用的。如下图所示,object5,object6,object7虽然相互有关联,但是它们到GC Roots是不可达的,所以他们将会被判定为不可回收对象。
Java中,可作为GC Roots的对象包括以下几种:
(1)虚拟机栈(Stack Frame中 本地变量表)中引用的对象。
(2)方法区中类静态属性引用的对象。
(3)方法去中常量引用的对象。
(4)本地方法栈中Native方法引用的对象。
3.2.2 再谈引用
判定对象是否存活与“引用”有关,引用分为强引用,弱引用,软引用,虚引用。
强引用:强引用是代码中普遍存在的类似“Object obj = new Object()”这类引用,只要强引用还存在,垃圾回收器永远不会回收掉被引用的对象。
软引用:软引用是用来描述一些还有用但是非必需对象。对于软引用关联着的对象,在系统将要发生内存溢出之前,将会把这些对象列进回收范围内进行第二次回收。如果这次回收还没有足够的内存,才会
抛出内存异常。Java后提供SoftReference类实现软引用。
弱引用:弱引用也是用来描述非必需对象,强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器开始工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的
对象。Java2后,提供WeakReference类描述弱引用。
虚引用:虚引用也被成为幽灵引用或幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为对象设置虚引用关联
的唯一目的是能在这个对象被收集器。
3.2.3 回收方法区
方法区(HotSpot虚拟机中的永久代)的垃圾收集效率远低于堆尤其是新生代中。
永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。
废弃常量:回收废弃常量与回收Java堆中的对象非常类似。以常量池中字面量的回收为例,加入一个字符串“abc”已经进入了常量池中,但是当前系统没有任何一个String对象是叫做“abc”的,换句话说,就
是没有任何String对象引用常量池中“abc”常量,也没有其他地方引用了这个字面量,如果这是发生内存回收而且必要的话,这个“abc”常量就会被系统清出常量池。常量池中的其他类(接口),方法,字段的符号
引用也与此类似。
无用的类的判定要下面3条件同时满足才行:
(1)该类的所有实例都已经被回收,也就是Java堆中不存在该类的任何实例。
(2)加载该类的ClassLoad已经被回收。
(3)该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该方法。
而如果同时满足以上三个条件,只是说可以回收,不代表一定回首,具体是否回收还需要自己控制。