哪些内存需要回收
当垃圾收集器对堆进行回收前, 首先要确定当前对象是否需要被回收, 所以第一步, 死亡判定
, 何为死亡呢, 就是说当前这个对象没有被其他地方引用他, 他用不到了,没作用了, 就判定死亡。
死亡判定的两种算法,
引用计数器, 记录该对象被引用的次数, 实现简单, 缺点明显, 无法解决循环引用的问题。
可达性算法, 该算法也是主流JVM所使用的, 我们会生成一系列的GC Roots, 像这个样子
判断对象是否存活就是寻找引用链, 是否可达GC Roots,那么就要问了, GC Roots是什么呢
GC Roots 也是堆中我们生成的对象, 只是这个对象有被下面几个地方引用到, 那么就可作为GC Roots
- 虚拟机栈的栈帧中, 即方法中所引用的对象, 这点好理解吧, 我们在使用一个方法的时候, 可能会new几个对象出来, 那么这些对象都可以认为是GC Roots, 这里的GC Roots 会随着方法出栈而退出GC Root 集合, 就有被回收的机会了。
- 方法区中常量引用的对象, 方法区,你又可以理解为永久代, 那么这里又是常量所引用的, 地址不会发生变化, 那么自然可以被当做GC Roots了, 个人认为这个GC Roots可以一直活着。
- 本地方法栈引用的对象, 和一同理
在jdk1.2,对引用的那根线, 也有了定义
对于引用呢, 我们有4种引用方式
- 强引用, new 的方式, 都是强引用, 垃圾收集器永远不会回收被引用的对象
- 软引用 内存即将发生溢出异常的时候, 会去先把这些引用回收掉, 看看还会不会溢出
- 弱引用, 垃圾收集器工作时, 每次垃圾回收的时候,都会回收掉这个对象
- 虚引用, 弱的不行, 能干啥我也不知道
关于四个引用使用场景, 后来会再说的。
那么除了对堆的回收, 方法区也是会有回收的, 主要针对废弃常量和无用的类, 回收条件较苛刻, 还得调JVM参数
怎么回收这些对象
我们之前有点东西忘说了, 就是在进行可达性分析的时候, 会对要回收的对象进行标记, 标记了的就要被回收, 针对回收方式, 由以下算法演进
-
标记-清除算法
将需要回收的对象标记, 然后直接清除
别看图是很规整的, 堆中分配的空间是由对象决定的, 所以呢, 这样的标记清除, 则会让内存碎片化严重, 这时候创建对象呢, 就是向这些回收的后的空隙内存中选取合适大小的块来分配, 如果这个对象需要很大的内存块,而又没有找到, 那么就尴尬了, 对吧。
-
复制算法
将内存分为两块, 每次回收, 将存活对象扔到另一边, 清除用过的那块
就是两块内存换着用, 来回倒, 另一块直接清0, 当然, 这样做缺点也很明显, 浪费了一整半空间, 再讲分代收集的时候, 我们会继续谈此算法。 标记-整理
很类似于标记清除, 只是直接移动存货对象到一块, 然后整体清零边界, 像这个样子
- 分代收集
啊哈, 分代收集和我们之前的对象头扯到一块了, 我们知道对象头里包含了对象的分代年龄, 对于不同的年龄呢, 我们把GC堆分为了新生代, 和老年代, 新生代回收都能回收走大量的对象, 老年代一般死亡的对象很少, 因此呢, 针对这样的问题, 新生代采用复制算法(优化过的, 针对新生代需进行大量回收),老年代采用标记整理或标记清楚。
优化的复制算法长啥样呢, 我们先来描述一下, 将原本的一半一半, 变成了8-1-1, 我们清楚, 新生代伴随大量内存回收, 存活下来的比较少, 每次使用8-1 块, 然后, 回收的时候, 将存活的复制到另一块小区域中, 并且清除8-1块, 就不画图了。这里要注意, 有时候可能存活的不止1的比例, 超出1的呢, 会通过分配担保机制, 提前进入老年代。