垃圾收集的第一步就是先需要算法来标记哪些是垃圾,然后再对垃圾进行处理。
引用计数(ReferenceCounting)算法
这种方法比较简单直观,FlashPlayer/Python使用该算法,简单高效。核心思路是,给每个对象添加一个被引用计数器,被引用时+1,引用失效-1,等于0时就表示该对象没有被引用,可以被回收。但是,Java/C#并不采用该算法,因为该算法没有解决对象相互引用的问题,即:当两个对象相互引用且不被其它对象引用时,各自的引用计数为1,虽不为0,但仍然是可被回收的垃圾对象。
根搜索(GC Roots Tracing)算法
基本原理是:GCRoot对象作为起始点(根)。如果从根到某个对象是可达的,则该对象称为“可达对象”(存活对象,不可回收对象)。否则就是不可达对象,可以被回收。
GC Root 引用
- JavaStack中的引用的对象。
- 方法区中静态引用指向的对象。
- 方法区中常量引用指向的对象。
- Native方法中JNI引用的对象。
垃圾收集算法
1 复制算法(copying)
内存分为两部分a,b
新生代被分配到a,a用完后,把a存活复制到b,清理a所有对象,同理b用完,复制到a
优点:简单高效,缺点 内存是只使用了一半
一般使用在新生代,适用内存分配频繁,eden:s0:s1 = 8:1:1
2 标记-整理(mark-compact)
标记存活对象
整理:将存活对象向内存开始部位移动
3 标记-清除(mark-sweep)
标记是否可回收
删除对象引用
缺点:内存空间碎片化效率不高
4 分代收集策略
Generation代
- YongGeneration/NewGeneration:新生代,在Eden/S0/S1的存活的对象。
- OldGeneration:老年代,在Tenured区存活的对象。
- PermanentGeneration:永久代。
Space 区
- PermanentGeneration:永久代。
Space 区
- Eden:伊甸园区,是新生代的一个区。
- Survivor:幸存区,属于新生代,为了复制算法的需要。一般分成大小相等的两个区(S0/S1或者From/To)。
- Survivor:幸存区,属于新生代,为了复制算法的需要。一般分成大小相等的两个区(S0/S1或者From/To)。
- Tenured:存放老年代的区域。
- Permanent:终身区。
- Permanent:终身区。
下图:Hotspot 的 Heap 分区

下图:VisualVM 中通过 VisualGC插件显示的分区

Eden/S0/S1 新生代
[Eden ][S0 ][S1 ]
S0/S1是大小相当的两个区域,共同组成Survivor区。
空间比例:Eden:S0==8:1。设定方法:-XX:SurvivorRatio=8。
新生对象在Eden/S0或者Eden/S1中分配,Eden区的对象量达到一个阈值后,发生一次新生代GC。
Old 老年代
每个对象有“对象年龄计数器”。对象由Eden收集到Survivor区后,年龄+1。进行新生代GC后,年龄+1。依次,当年龄>=15后进入老年代。
最大年龄阈值设定:-XX:MaxTenuringThreshold。
动态年龄:如果在Survivor中所有相同年龄对象占用了空间的一半多,大于等于上述年龄的对象直接进入老年代。
大对象(比如大的数组)直接进入老年代。阈值设定:-XX:PretenureSizeThreshold。
Perm 永久代(PermanentGeneration)
用于存放不变对象,如类、方法、字符串等。
Java7把驻留字符串(intentd string)放到了老年代区。Java8中移除了Hotspot的永久代区。