1.什么场景下该使用什么垃圾回收策略?
(1).对内存要求苛刻的场景
想办法提高对象的回收效率,尽可能的多回收一些对象,腾出更多内存
(2).在CPU使用率较高的情况下
降低高并发时垃圾回收频率,让CPU更多地去执行业务而不是垃圾回收
2.垃圾回收会发生在哪些区域?
线程共享区域的堆和方法区
3.如何判断对象可以被回收?
(1).引用计数法
每个对象都有一个引用计数器,新增一个引用,引用计数器加1,释放一个引用时,引用计数器减1,当引用计数器的值为0时可以回收。
引用计数法,可能会出现循环引用,如A引用了B,B又引用了A,导致A和B的引用计数器永远都是1,即时A和B不再使用了,也无法被回收。
(2).可达性分析法
从GC ROOTS对象开始向下搜索,搜索所走过的路径称为引用链。如果一个对象到GC ROOTS对象没有任何引用链相连时,则证明此对象是不可用的,那么JVM就判断该对象是可回收的。
可达性算法中不可达的对象并不是立即死亡的,而是需要经过如下判断。首先判断对象是否覆盖了finalize()方法,如果没有则直接进行回收。如果覆盖了finalize()方法,则需要继续判断是否执行过finalize()方法,如果没有执行,则将该对象放入F-Queue中等待被执行,如果执行过finalize()方法,则需要再判断一次该对象是否可达,如果不可达则回收,如果可达则复活。
(3).GC Roots对象有哪些?
虚拟机栈中引用的对象
本地方法栈中引用的对象
方法区中静态变量引用的对象
方法区中常量引用的对象
(4).引用
强引用:只要强引用在,就不会被回收。Object object = new Object()。
软引用:描述有用但是非必需的对象,只有在内存不足的时候才会被回收。
弱引用:描述非必需的对象,不管内存是否足够,都会被回收。
虚引用:如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被回收。
4.垃圾回收算法
(1).标记清除(Mark-Sweep)
标记清除算法分为两个阶段,一是标记阶段,把垃圾标记出来,二是清除阶段,把内容回收。这种算法简单,但是有个严重的问题,就是会产生大量的内存碎片。
(2).标记整理算法(Mark-Compact)
标记整理算法在标记阶段和标记清除算法一样,但是在完成标记之后,不是直接清理,而是将存活的对象往一端移动,然后将端边界以外的内存直接清除。
(3).复制算法(Copy)
复制算法将内存分为大小相等的两半,每次只使用其中的一半。垃圾回收时,将正在使用的这一半区域里的存活对象复制到未使用的一半区域去,然后将含有垃圾的那一半内存直接清除。这种算法不存在内存碎片,但是最大的问题是浪费空间,内存利用率低。
(4).三种算法对比
复制 | 优点 | 缺点 |
标记清除 | 简单 | 存在内存碎片 |
标记整理 | 无内存碎片 | 整理存在开销 |
复制 | 无内存碎片 | 内存利用率低 |
(5).增量算法
内存是一块比较大的区域,如果对整片内存进行回收,工作量比较大,可能会造成卡顿。因此,增量算法的思路是,每次只回收一小块内存里的垃圾。
5.分代收集算法
(1).概念
根据对象的存活周期,将内存分为多个区域,不同的区域使用不同的回收算法回收对象。
(2).分代回收分类
新生代回收(Young GC):复制算法
老年代回收(Major GC):标记清除和标记整理
整堆回收(Full GC)
(3).一个对象在堆内存的经历过程
对象首先会分配在堆内存中新生代的Eden区,然后经过一次Young GC后,如果还存活,就会进入Survivor区。在后续的每次Young GC后,如果对象一直存活,那么该对象就会在
Survivor的两个区域来回复制,每复制一次,年龄就加1,当年龄为15时,就会转入老年代。
对象进入老年代的两种特殊情况,一是如果新生代空间不够,也会将对象直接分配到老年代,因为新生代采用复制算法,大对象需要在Survivor区来回复制,开销较大。二是,对象年龄不一定要达到15才进入老年代,如果Survivor空间中所有相同年龄对象的大小总和大于Survivor空间的一半,那么年龄大于等于该年龄时的对象就可以直接进入老年代。
(4).触发机制
新生代回收(Young GC):新生代空间不足
整个堆回收(Full GC)):老年代或元空间内存不足(要晋升到老年代对象的空间大于老年代剩余空间),以及显示调用System.gc()。