Garbage Collection
Java 自动内存管理主要针对对象的分配与回收。Java 自动回收内存最主要的区域是堆内存,Hotspot 虚拟机在1.8之前将内存回收拓展到了方法区(永久代)。

JDK1.8 之前的堆内存示意图
上图来源网上,我认为不精确,因为永久代是方法区,方法区在 JVM 规范里面不属于堆(虽然Hotspot 可能用堆来实现的方法区)。
可以看出垃圾回收区域的分为新生代、老年代和永久代。新生代又被进一步分为:Eden 区+Survior1 区+Survior2 区。在 JDK 1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。
大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够的内存时,将先触发一次 Minor GC。
- Minor GC:指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。
- Full GC(Major GC):指发生在老年代的GC,出现了Major GC经常会伴随至少一次的Minor GC(并非绝对),Major GC的速度一般会比Minor GC的慢10倍以上。
为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率,所以直接进入老年代,比如需要大量连续内存空间的对象(字符串、数组)。
虚拟机给每个对象一个对象年龄(Age)计数器,如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为1。对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1,当它的年龄增加到一定程度(默认为15),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数
-XX:MaxTenuringThreshold
来设置。动态年龄判断:虚拟机不是永远要求对象年龄必须达到了某个值才能进入老年代,如果 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需达到要求的年龄。
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使用的。
JVM 并没有使用此方法,原因是存在循环引用的问题。
通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
GC Roots 包括:
- 虚拟机栈中的引用的对象(本地变量和入参)。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中 JNI 引用的对象。

不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,对象死亡,要经历两次标记过程。可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。

引用计数法与可达性分析都与引用相关。JDK1.2 之后,将引用分 为强引用、软引用、弱引用、虚引用。
FinalReference
为包可见,其它为 public
。我们平时使用的引用即为强引用,如果对象具有强 引用,那么垃圾收集器不会回收它,当内存不足时,宁愿抛出 OutOfMemoryError 异常。对于一个普通对象引用,如果没有其它引用关系,只要超过了引用的作用域或者显示将引用赋值为
null
,就可以被垃圾收集了。如果一个对象只具有软引用,如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。通常用来实现内存敏感的缓存。
软引用在最后一次引用后,还能保持一段时间,默认根据堆剩余空间计算的。可以通过参数
-XX:SoftRefLRUPolicyMSPerMB
修改。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
WeakHashMap
使用弱引用作为内部数据的存储方案。不能通过虚引用访问对象,仅仅提供一种对象在被 finalize 后做某些事情的机制。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。用
java.lang.ref.PhantomReference
表示。虚引用必须和引用队列ReferenceQueue
关联使用。
对象可达状态流转
java.lang.ref.Reference.get()
的实现方法里面,除了虚引用会永远返回 null,其它都可以得到原有对象,所以软引用和弱引用都可以重新指向强引用。所以对于软引用和弱引用,垃圾收集器可能会存在二次确认,以保证没有改变为强引用。
Java 1.9 的
java.lang.ref.Reference
提供了一个新的方法reachabilityFence
,这个底层的 API 的作用是强制使对象处于强引用状态,就算没有显示的引用指向这个对象。class Resource {
public void action() {
try {
// do something
} finally {
// 调用 reachbilityFence,明确保障对象 strongly reachable
Reference.reachabilityFence(this);
}
}
}
如上面的例子,若没有调用
reachabilityFence
,则new Resource.action()
执行后,Java 可以合法地回收这个对象,但是现在就不行了。这种书写方式在异步编程中很常见。在可达性分析中不可达的对象,并不是“非死不可”,对象的死亡至少需要经历两次标记。
经过可达性分析,若对象没有与 GC Roots 相连,会被第一次标记,并判断是否需要执行 finalize() 方法(同时满足以下两个条件):
- 是否重写了 finalize() 方法。
- 是否已经执行过 finalize() 方法。
若判断需要执行 finalize(),则对象会被放置在 F-Queue 中,稍后由一个低优先级的 Finalizer 线程在执行。
如果对象在 finalize() 方法中重新与引用链上的任何一个对象建立关联,那么对象就可以不被回收。
不建议使用 finalize() 方法,理由如下:
- 运行代价高。
- 不确定性大。
- 无法保证各对象的调用顺序。
- 基本可用 try-finally 或其它方式替代。
假如在常量池中存在字符串 "abc",如果当前没有任何String对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池。
JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
同时满足以下三个条件:
- 该类所有的实例都已经被回收。
- 加载该类的 ClassLoader 已经被回收。
- 该类对应的 java.lang.Class 对象没有在任何地方被引用。
虚拟机可以对满足上述3个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。
算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
- 效率很高。
- 清除后有很多不连续的碎片。

收集器:CMS。
将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。
