垃圾回收机制
对象创建过程
对象内存分布
对象的访问
对象回收算法
-
引用计数法:可能存在的问题,对象相互引用
-
可达性分析:
可达性分析算法的主要思路是先找出一批根节点对象集合作为GC Roots(可称为根节点枚举),然后从这批根节点出发,查找其引用关系(类似于深度优先搜索),最终形成如下图这样的反映对象间依赖关系的图,若某些对象没有任何引用链与GC Roots相连(如下图中的Object5,Object6,Object7),则说明这些对象就是垃圾对象,是可以被垃圾收集器回收的。
如下这些对象就可以作为GC Roots对象:
1、虚拟机栈中引用的对象(参数,局部变量等)
2、方法区中类静态变量
3、方法区中常量引用的对象
4、本地方法栈中引用的对象
5、被同步锁(synchronized)持有的对象
finalize方法
-
当对象即将被回收的时候,会回调这个方法,这个方法只会被回调一次
-
finalize方法异步调用并且优先级比较低
引用类型
- 强引用
- 软引用(SoftReference):对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象,这个特性很适合用来实现缓存:比如网页缓存、图片缓存等
- 弱引用(WeakReference):当JVM进行垃圾回收时,无论内存是否充足,gc时都会回收被弱引用关联的对象,ThreadLocal里面的ThreadLocalMap就是用了WeakReference来保存ThreadLocal对象
- 虚引用(PhantomReference):随时随地会被回收,
对于强引用,我们平时在编写代码时经常会用到。而对于其他三种类型的引用,使用得最多的就是软引用和弱引用,这2种既有相似之处又有区别。它们都是用来描述非必需对象的,但是被软引用关联的对象只有在内存不足时才会被回收,而被弱引用关联的对象在JVM进行垃圾回收时总会被回收。针对上面的特性,软引用适合用来进行缓存,当内存不够时能让JVM回收内存,弱引用能用来在回调函数中防止内存泄露。
因为回调函数往往是匿名内部类,隐式保存有对外部类的引用,所以如果回调函数是在另一个线程里面被回调,而这时如果需要回收外部类,那么就会内存泄露,因为匿名内部类保存有对外部类的强引用。
堆内存分布
堆被划分成两部分:新生代和老年代,新生代和老年代比值为1:2
,这个比例并不是唯一的,我们可以可以通过参数 –XX:NewRatio
按照具体的场景来指定。
新生代又可以分为 Eden区
和Survivor区
,而Survivor区
又可以分为FromSurvivor
和ToSurvivor
,默认比值为8:1:1
对象分配策略
其实Java中的对象并不是全部分配在堆内存区域,也可能分配在栈中。一个对象创建后的分配顺序如下:
1、尝试进行栈上分配(线程私有小对象,对象无逃逸,并且支持标量替换),栈上分配的对象会随着方法结束栈帧弹出而消亡,无需等到GC去清理;
2、栈上分配失败,如果是大对象,则直接分配到堆中Old区;
3、如果是小对象,优先进行线程私有本地分配(Thread Local Allocation Buff,文末解释);
4、线程私有本地分配失败,则分配在堆中Young区中的Eden区。
涉及点:
Thread Local Allocation Buff,简称 TLAB,线程私有本地分配。在Eden区中,由于空间是线程共享,会导致多个线程同时去竞争Eden区中位置而降低效率。为了减少这种情况,每个线程会在Eden区中获取一块私有空间(默认1%,JVM参数可调),线程上的私有小对象会优先分配到这里,避免多个线程同时竞争一个位置,提高效率。TLAB也是位于Eden区中。
验证,JVM添加启动参数-XX:-DoEscapeAnalysis -XX:+PrintGC,禁止对象分配在堆上,并且打印GC
public class Test {
public static void main(String[] args) {
long b = System.currentTimeMillis();
for (int i = 0; i < 5000000; i++) {
test();
}
System.out.println(System.currentTimeMillis() - b);
}
private static void test() {
new String("233333");
}
}
//-静止分配在堆上打印
[GC (Allocation Failure) 65536K->616K(251392K), 0.0007247 secs]
52
//允许分配在堆上打印
4
垃圾回收算法
- Minor GC/Young GC:从年轻代空间(包括 Eden 和 Survivor 区域)回收内存
- Full FC:清理整个空间,包括方法区
- Major GC/Old GC:清理老年代
GC 复制算法(Copying GC)
通过将内存分为两个空间,在垃圾回收是将空间1的活动对象,直接复制到空间2中,然后全部回收掉空间1的对象
- 实现简单,运行效率高
- 内存复制,没有内存碎片
- 利用率只有一半
改进版本:Appel式回收
新生代分为一块较大的Eden空间和两块较小的 Survivor空间,每次分配内存只使用Eden和其中一块Survivor,三个区域,Eden,From,To
标记-清除算法(Mark-Sweep)
由标记阶段
和清除阶段
构成。在标记阶段会把所有的活动对象都做上标记,然后在清除阶段会把没有标记的对象,也就是非活动
对象回收
- 执行效率不稳定
- 内存碎片可能导致提前GC
标记-整理算法(Mark-Compact)
两个阶段:标记和整理,在标记阶段会把所有的活动对象都做上标记,整理阶段将对象按顺序排列
- 没有内存碎片
- 用户线程暂停
常见垃圾收集器
HotSpot虚拟机所有的垃圾收集器如下图
上面有7种收集器,分为部分,上面为新生代收集器,下面是老年代收集器。如果两个收集器之间存在连线,就说明它们可以搭配使用。
收集器 | 收集对象和算法 | 收集器类型 |
---|---|---|
Serial | 新生代,复制算法 | 单线程 |
ParNew | 新生代,复制算法 | 并行的多线程 收集器 |
Parallel Scavenge | 新生代,复制算法 | 并行的多线程 收集器 |
收集器 | 收集对象和算法 | 收集器类型 |
---|---|---|
Serial Old | 老年代,标记整理 算法 | 单线程 |
Parallel Old | 老年代,标记整理 算法 | 并行的多线程收集器 |
CMS | 老年代,标记清除 算法 | 并行与并发收 集器 |
G1 | 跨新生代和老年代; 标记整理 + 化整为零 | 并行与并发收 集器 |
简单垃圾回收器示意图
CMS使用的是标记清除算法,会带来内存碎片。
Stop The World
指的是GC事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为STW