1、对象是否已死
通过对象是否已死来决定要不要回收该对象。判断对象是否已死的算法有:引用计数算法、可达性分析算法。
1.1、引用计数算法
简单描述引用计数算法,对每个对象一个引用计数器,被引用就+1,引用失效-1,计数器为0就不可能再被引用。其优缺点如下:
- 优点 :
- 实现简单,效率高;
- 实时性高,无需等到内存不够的时候,才开始回收
- 在垃圾回收过程中对象无需挂起。如果申请内存时,内存不足,直接报OutOfMember。
- 区域性:更新对象的计数器时,只是影响到该对象,不会扫描全部对象。
- 缺点:
- 每次对象引用时,都需要去更新计数器,有一点时间开销。
- 浪费CPU资源,既是内存够哦用,仍然在运行时进行计数器的统计。
- 无法解决循环引用问题。(最大缺陷)不能解决对象间互相循环引用的问题;
所以主流的java虚拟机未选用该算法来管理内存。
1. 2、可达性分析算法
主流的商用语言java、C#等都是通过可达性分析算法来判断对象是否存活。基本实现思想为通过“GC Roots”作为起始点,向下搜索,走过的路径成为引用链“Reference Chain”,如果对象到“GC Roots”没有引用链相连,那该对象可回收。如图:
GC Roots对象包括
- 虚拟机栈中引用的对象
- 方法区中静态属性引用的对象(1.8之后移至元空间)
- 方法区中常量引用的对象(1.8之后移至元空间)
- 本地方法栈中JNI(即一般说的Native方法)引用的对象
拓展-
强引用(Strong Reference):
开发中最常见的引用,如:Object obj = new Object();
-
软引用(Soft Reference)
软引用需要专门的声明,如:SoftReference<String> str = new SoftReference<String>("hello");
软引用关联的对象在内存不足时被回收;
适合用来做缓存; -
弱引用(Weak Reference)
弱引用也需要专门声明,例如:WeakReference<String> str = new WeakReference<String>("hello");
被弱引用关联的对象每次GC都会被回收;
弱引用最常见的用途是实现可自动清理的集合或者队列; -
虚引用(Phantom Reference)
也需要专门声明,如:PhantomReference<String> phantom = new PhantomReference<>(new String("hello"), new ReferenceQueue<>());
虚引用是最弱的引用,完全不影响对象的生存空间,唯一的作用是对象被回收时发一个通知;
-
1.3 起死回生
对象被判定死亡(即不可达)后,不会被立即回收,经过两次标记才会被回收。注:finalize()方法只会被调用一次。该方法代价高昂,不建议使用 。如图:
2、垃圾收集算法
主要包含标记-清除算法、复制算法、标记-整理算法、分代收集算法。
2.1、标记-清除算法
分两个步骤:
- 标记:从根节点开始标记非垃圾对象,即前面1.2降到的可达性分析算法;
- 清除:清除所有未被标记的对象;
执行过程如图:
- 优点:
- 解决了内存虚幻利用的问题;
- 缺点:
- 效率低,标记和清除都需要遍历所有的对象;
- 内存碎片太多,内存不连贯,较大对象分配内存无法找到连续可用内存,会提前出发GC;
2.2 复制算法:
将可用内存分为相等的两块A/B,每次只使用A,A内存不足时,将A中或者的对象拷贝到B上,将A清空,A/B调换位置。
- 优点:实现简单、(对象存活率较低时)运行高效;
- 缺点:
- 内存缩小为原来的一半;
- 对象存活率较高时,复制操作频繁,效率会降低
2.3 标记整理算法
针对老年代,标记过程与“标记-清除算法”一样,让所有存货的对象向一端移动,清理掉端边界以外的内存。如下图:
2.4 分代收集算法
- 当前商业虚拟机的垃圾收集都是用分代收集算法。
- 根据对象存活周期不同,对内存进行划分,针对不同区域采取不同算法;
- java堆分为新生代、老年代;
- 新生代,朝生夕死,用复制算法;
- 老年代:标记-清理或标记整理
3 HotSpot 的GC垃圾收集器
- 没有最好的牢记收集器,更加没有万能的垃圾收集器,只能选择对具体应用最适合的;
- HotSpot包含的收集器如下,有连线的部分是可以组合使用:
- GC组合使用总结
GC组合 | Minor GC | Major GC/Full GC | 描述 |
---|---|---|---|
-XX:+UserSerialGC | Serial收集器串行回收(单线程) | Serial Old收集器串行回收(单线程) | 该选项可以手动指定Serial收集器 +Serial Old收集器组合执行内存回收,多用于Client应用 |
-XX:+UserParNewGC | ParNew收集器并行回收(多线程) | Serial Old收集器串行回收(单线程) | 该选项可以手动指定ParNew收集器 +Serial Old收集器组合执行内存回收 |
-XX:+UserParallelGC | Parallel收集器并行回收(多线程) | Serial Old收集器串行回收(单线程) | 该选项可以手动指定Parallel收集器 +Serial Old收集器组合执行内存回收 |
-XX:+UserParallelOldGC | Parallel收集器并行回收(多线程) | Parallel Old收集器并行回收(多线程) | 该选项可以手动指定Parallel收集器 +Parallel Old收集器组合执行内存回收 |
-XX:+UserConcMarkSweepGC | ParNew收集器并行回收(多线程) | 默认使用CMS收集器并发回收,备用采用Serial Old收集器串行回收(单线程) | 默认指定ParNew收集器 + CMS +Serial Old 收集器组合执行内存回收。先使用ParNew + CMS,当出现ConcurrentMode Fail 或Promotion Failed时,采用ParNew + Serial Old |
-XX:+UserG1GC | G1收集器并发、并行执行内存回收 | G1收集器并发、并行执行内存回收 |
注:
- minor GC:新生代的垃圾回收;
- major GC:也叫作full GC,老年代的垃圾回收。
名称解释
1、安全点、安全区域
2、并发、并行
并发和并行都是并发编程中的概念,结合垃圾收集器的语境解释如下:
- 并行(Parallel):指多条垃圾收集器线程并行工作,但此时用户线程处于等待状态;
- 并发(Concurrent):指用户线程与垃圾收集器线程同时运行(不一定并行,可能交替执行),用户程序继续运行,垃圾收集程序运行于另一CPU。
3、吞吐量
吞吐量 = 运行用户代码的时间 / (运行代码的时间 + 垃圾收集的时间)。
如:虚拟机总共运行100分钟,垃圾回收1分钟,吞吐量就是99%。
3.1 Serial收集器
采用复制算法的新生代收集器,单线程。垃圾回收时,stop the word ,多用于Client应用;
- 优点:
- 与其他收集器的单线程相比,对单个CPU来说,无线程交互开销,简单高效;
- ** 缺点**:
- 垃圾回收时,停止所有线程,对于除桌面应用之外的其他应用来说,stop the word时间太长
- Serial / Serial Old收集器的运行过程如下:
3.2 ParNew收集器
Serial的多线程版本,同样是采用复制算法新生代并行收集器。
- 优点:
- 多CPU场景下,效率高于Serial收集器;
- 除Serial收集器外,目前只有ParNew能与CMS收集器配合工作;
- 缺点:
- 单CPU场景下,增加了线程开销,效率低于Serial收集器;
- 与Serial Old收集器配合使用,如图:
3.3 Parallel Scavenge 收集器
采用复制算法的新生代并行收集器。目标不同于其他垃圾收集器想要控制用户线程停顿时间,Parallel Scavenge 的目的是可控吞吐量,并提供了两个参数精确控制吞吐量:
- -XX:MaxGCPauseMillis(最大垃圾手机停顿时间):
允许值为大于0的毫秒数。GC的停顿时间是以牺牲吞吐量和新生代空间换取的,所以这个值不是越小越好。 - -XX:GCTimeRatio(吞吐量大小):
允许值为0-100之间的整数。