垃圾回收概述
垃圾收集(Garbage Collection, 下文简称GC)需要完成的三件事情:
哪些内存需要回收?
什么时候回收?
如何回收?
java垃圾回收的优缺点:
- 优点:
a.不需要考虑内存管理,
b.可以有效的防止内存泄漏,有效的利用可使用的内存,
c.由于有垃圾回收机制,Java中的对象不再有”作用域”的概念,只有对象的引用才有”作用域”
- 缺点:
java开发人员不了解自动内存管理, 内存管理就像一个黑匣子,过度依赖就会降低我们解决内存溢出/内存泄漏等问题的能力。
判断对象是否已死算法
引用计数算法
给每个创建的对象添加一个引用计数器,每当此对象被某个地方引用时,计数值+1,
引用失效时-1,所以当计数值为0时表示对象已经不能被使用。
优点:
实现简单,执行效率高,很好的和程序交织。
缺点:
无法检测出循环引用。
可达性分析算法
概述
通过“GC Roots”的对象作为起始点,从起始点开始向下搜索到对象的路径。
搜索所经过的路径称为引用链(Reference Chain),当一个对象到任何GC Roots都没有引用链时,则表明对象“不可达”,即该对象是不可用的
可以作为GC Root的对象:
- 栈帧中的局部变量表中的reference引用所引用的对象
- 方法区中static静态引用的对象
- 方法区中final常量引用的对象
- 本地方法栈中JNI(Native方法)引用的对象
- Java虚拟机内部的引用, 如基本数据类型对应的Class对象, 一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError) 等, 还有系统类加载器。
- 所有被同步锁(synchronized关键字) 持有的对象。
JVM之判断对象是否存活
finalize()方法最终判定对象是否存活:
可达性分析算法中判定为不可达的对象,需要通过finalize()方法判断,才会进入垃圾回收队列,等待回收。
具体过程:
- 第一次标记:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链, 会被第一次标记。随后,基于是否有必要执行finalize()方法,进行一次筛选
- 有必要:该对象重写了finalize()方法,并且该方法未执行过
- 在finalize()方法中重新建立引用关系—逃过本次垃圾回收
- 在finalize()方法中未建立引用—继续被回收
- 没有必要:对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过—继续被回收
- 有必要:该对象重写了finalize()方法,并且该方法未执行过
引用类型
- 强引用StrongReference
- 最普遍的引用,不会被垃圾回收器回收
- A a = new A();
- 软引用SoftReference
- 只具有软引用的对象,内存空间足够,垃圾回收器不会回收它;如果内存空间不足,就会回收该对象的内存
- SoftReference
- 弱引用WeakReference
- 当垃圾收集器开始工作时,无论当前内存是否足够, 都会回收掉只被弱引用关联的对象
- 虚引用
- 如果一个对象仅持有虚引用,在任何时候都可能被垃圾回收器回收。
- 虚引用主要用来跟踪对象被垃圾回收器回收的活动。
软引用、弱引用、虚引用都可以设置一个相关的引用队列,在被垃圾回收器回收前,会将该引用加入到相关的队列中去。
垃圾收集算法
分代收集理论
据对象的生命周期将内存划分,然后进行分区管理。
建立在两个分代假说之上:
1) 弱分代假说(Weak Generational Hypothesis) : 绝大多数对象都是朝生夕灭的。
2) 强分代假说(Strong Generational Hypothesis) : 熬过越多次垃圾收集过程的对象就越难以消亡。
标记-清除算法
算法分为“标记”和“清除”两个阶段:
- 首先标记出所有需要回收的对象
- 在标记完成后,统一回收掉所有被标记的对象, 也可以反过来, 标记存活的对象, 统一回收所有未被标记的对象。
缺点:
- 执行效率不稳定 ,如果堆中包含大量对象,其中大部分需要回收,需要进行大量标记和清除动作
- 内存空间的碎片化
标记-复制算法
产生原因:为解决标记-清除算法面对大量可回收对象时执行效率低的问题
概述:将可用内存按容量划分为大小相等的两块, 每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面, 然后再把已使用过的内存空间一次清理掉。
缺点:
- 需要提前预留一半的内存区域用来存放存活的对象,导致可用的对象区域减小一半,总体的GC更加频繁
- 如果出现存活对象数量比较多的时候,需要复制较多的对象,成本上升,效率降低
- 如果99%的对象都是存活的(老年代),那么老年代是无法使用这种算法的。
标记-整理算法
产生原因:针对老年代对象存活率较高的特征,针对性提出的算法
概述:首先标记出存活的对象,让所有存活的对象都向内存空间一端移动, 然后直接清理掉边界以外的内存
是否移动对象都存在弊端, 移动则内存回收时会更复杂, 不移动则内存分配时会更复杂。 从垃圾收集的停顿时间来看, 不移动对象停顿时间会更短, 甚至可以不需要停顿, 但是从整个程序的吞吐量来看, 移动对象会更划算。
垃圾收集器
垃圾收集器
垃圾回收器与垃圾回收算法
垃圾回收算法分类两类:
- 判断对象生死算法
- 引用计数算法
- 可达性分析算法
- 收集死亡对象算法
- 标记-清除算法
- 标记-复制算法
- 标记-整理算法
垃圾收集器是算法的落地实现
垃圾收集器分类
七种垃圾收集器及其组合关系
根据分代思想,我们有7种主流的垃圾回收器
新生代垃圾收集器:Serial 、 ParNew 、Parallel Scavenge
老年代垃圾收集器:Serial Old 、 Parallel Old 、CMS
整理收集器:G1
垃圾收集器的组合关系
JDK8中默认使用组合是: Parallel Scavenge GC 、ParallelOld GC
JDK9默认是用G1为垃圾收集器
GC性能指标
吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值
(吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间 ))
例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%
暂停时间:执行垃圾回收时,程序的工作线程被暂停的时间
内存占用:java堆所占内存的大小
收集频率:垃圾收集的频次