本文共 2965 字,大约阅读时间需要 9 分钟。
所谓垃圾就是内存中已经没有用的对象。 既然是”垃圾回收",那就必须知道哪些对象是垃圾。Java 虚拟机中使用一种叫作可达性分析的算法来决定对象是否可以被回收。
JVM
把内存中所有的对象之间的引用关系看作一张图,通过一组名为 GC Root
的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,最后通过判断对象的引用链是否可达来决定对象是否可以被回收。如下图所示:
注意:上图中圆形图标虽然标记的是对象,但实际上代表的是此对象在内存中的引用。包括 GC Root 也是一组引用而并非对象。
在 Java 中,有以下几种对象可以作为 GC Root:
注意:全局变量同静态变量不同,它不会被当作 GC Root。
不同的虚拟机实现有着不同的 GC 实现机制,但是一般情况下每一种 GC 实现都会在以下两种情况下触发垃圾回收:
Allocation Failure
:在堆内存中分配时,如果因为可用剩余空间不足导致对象内存分配失败,这时系统会触发一次 GC。System.gc()
:在应用层,Java 开发工程师可以主动调用此 API 来请求一次 GC。从 GC Roots
集合开始,将内存整个遍历一次,保留所有可以被 GC Roots
直接或间接引用到的对象,而剩下的对象都当作垃圾对待并回收,过程分两步;
Mark 标记阶段
:找到内存中的所有 GC Root 对象,只要是和 GC Root 对象直接或者间接相连则标记为灰色(也就是存活对象),否则标记为黑色(也就是垃圾对象)Sweep 清除阶段
:当遍历完所有的 GC Root 之后,则将标记为垃圾的对象直接清除stop the world
将现有的内存空间分为两快,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中。之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
需要先从根节点开始对所有可达对象做一次标记,之后,它并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端。最后,清理边界外所有的空间。因此标记压缩也分两步完成:
Mark 标记阶段
:找到内存中的所有 GC Root 对象,只要是和 GC Root 对象直接或者间接相连则标记为灰色(也就是存活对象),否则标记为黑色(也就是垃圾对象)。Compact 压缩阶段
:将剩余存活对象按顺序压缩到内存的某一端。新生代
、老年代
,这就是 JVM 的内存分代策略。 注意: 在 HotSpot 中除了新生代和老年代,还有
永久代
。
新生成的对象优先存放在新生代中,新生代对象存活率很低,在新生代中,常规应用进行一次垃圾收集一般可以回收 70%~95% 的空间,回收效率很高。新生代中因为要进行一些复制操作,所以一般采用的 GC 回收算法是复制算法
。
新生代又可以继续细分为 3 部分:Eden
、Survivor0(简称 S0)
、Survivor1(简称S1)
。这 3 部分按照 8:1:1
的比例来划分新生代。
Eden
区Eden
区第一次满的时候,会进行垃圾回收。首先将 Eden
区的垃圾对象回收清除,并将存活的对象复制到 S0
,此时 S1
是空的Eden
区满时,再执行一次垃圾回收。此次会将 Eden
和 S0
区中所有垃圾对象清除,并将存活对象复制到 S1
,此时 S0
变为空S0
和 S1
之间切换几次(默认 15 次
)之后,如果还有存活对象。说明这些对象的生命周期较长,则将它们转移到老年代中。一个对象如果在新生代存活了足够长的时间而没有被清理掉,则会被复制到老年代。老年代的内存大小一般比新生代大,能存放更多的对象。如果对象比较大(比如长字符串或者大数组),并且新生代的剩余空间不足,则这个大对象会直接被分配到老年代上。
我们可以使用 -XX:PretenureSizeThreshold
来控制直接升入老年代的对象大小,大于这个值的对象会直接分配在老年代上。老年代因为对象的生命周期较长,不需要过多的复制操作,所以一般采用标记压缩的回收算法
注意:对于老年代可能存在这么一种情况,老年代中的对象有时候会引用到新生代对象。这时如果要执行新生代 GC,则可能需要查询整个老年代上可能存在引用新生代的情况,这显然是低效的。所以,老年代中维护了一个 512 byte 的 card table,所有老年代对象引用新生代对象的信息都记录在这里。每当新生代发生 GC 时,只需要检查这个 card table 即可,大大提高了性能。
新生代和老年代所打印的日志是有区别的:
新生代 GC
:这一区域的 GC 叫作 Minor GC。因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。老年代 GC
:发生在这一区域的 GC 也叫作 Major GC 或者 Full GC。当出现了 Major GC,经常会伴随至少一次的 Minor GC。注意:在有些虚拟机实现中,
Major GC
和Full GC
还是有一些区别的。Major GC
只是代表回收老年代的内存,而Full GC
则代表回收整个堆中的内存,也就是新生代 + 老年代。
Android官方在对
SoftReference
的介绍中,也已经不建议使用它来实现缓存功能。
转载地址:http://ttpgi.baihongyu.com/