文章目录
堆和方法区的垃圾回收
- 由于程序计数器、虚拟机栈、本地方栈为线程私有。方法结束或者线程结束时,内存自然回收。
- 堆和方法区上的内存分配只有在运行期间才能确定,这部分内存的分配和回收都是动态的。
所以垃圾回收指的是Java堆和方法区上的内存回收,后面用GC表示垃圾回收
那何时触发GC???
当程序要创建对象时,内存区不能划分出足够的内存,这时候会触发一次GC
在开发中常遇到是:频繁创建对象,由于内存区一直分配内存,当剩余内存不足时就会触发一次GC。当GC后仍然不足以分配出去就会抛出OOM异常
触发GC时,如何判断对象是否存活(可回收)?
先介绍判断对象存活的几种方式
- 引用计数器法(未使用)
**原理:**每当有一个地方引用它时,计数器加1,引用消失则计数器减1,当计数器为0时,对象即可回收
**缺点:**两个对象互相引用时导致计数器不为0,无法回收
- 可达性分析算法(当前使用)
当一个对象到GC Root没有任何引用,则该对象可回收。
GC Root GC Root
| |
object1 object1
/ \ /
object2 object4 object2 object4
/ | / |
object3 object5 object3 object5
如图:从左到右,object4到GC Root的引用链断开,object4、object5会被判定为可回收。
可以视为GC Root的有4种:
-
虚拟机栈(栈帧中的本地变量表)中引用的对象
-
方法区中类静态属性引用的对象(静态变量)
-
方法区中常量引用的对象(常量)
-
本地方法栈中JNI(Native方法)引用的对象
引用概念的扩展
当对象可回收(不可达),而内存空间足够时,希望能保存这部分对象,于是出现强引用、软引用、弱引用、虚引用4种引用的概念,使可回收对象可以继续保存一段时间。
-
强引用:通过new关键字创建的对象为强引用对象,不会被垃圾收集器回收
-
软引用:发生内存溢出之前回收该类对象。多用于内存敏感的环境。软引用可以加快JVM垃圾回收的速度,防止内存溢出。
-
弱引用:垃圾收集器工作时,只要发现有弱引用的对象就会回收
-
虚引用:用于监测对象的是否已经回收。使用场景:NIO操作时使用直接内存->通过一个储存在堆中的DirectByteBuffer对象作为这块内存的引用进行操作->通过虚引用可以知道对象被回收时机->于是回收直接内存
既然对象已经可以回收了,我们来看下它是如何回收的
垃圾回收算法
先来看下每个算法的执行过程,白色为未分配的内存、灰色为已使用的内存、深灰色为已标记(可回收)内存
使用 | 已使用 | 已标记 |
标记-清除算法 (Mark-Sweep)
标记-清除算法是最基本的算法
回收前:通过可达性分析标记内存块
回收后:清除了带标记的内存块
缺点:
- 标记和清除过程效率不高
- 由上图可看到:因为回收后出现了大量内存碎片,导致无法分配大块连续的内存(比如:数组),导致触发下一次GC
复制算法(Copying)
为了弥补标记-清除算法的两个缺点,出现了复制算法,复制算法不在需要关注内存碎片且提高了回收效率
回收前:每次使用容量的一半
回收后:先复制存活的到另一块内存,然后清理一整块4x3的内存
优点:
- 非常适合用在对象存活率低的场景(新生代多的场景)–原理接下来介绍
- 效率高(只需要移动堆顶指针,按顺序分配即可)
缺点:
- 内存缩小为原来的一半
标记-整理算法(Mark-Compact)
相对于复制算法,标记-整理算法适用于对象存活率高的情况,它的关键在于整理
回收前:将存活对象移向一端,清理以外的内存
回收后:存活的对象聚集在一端
优点:
- 适合用在对象存活率高的场景(老年代多的场景)
垃圾收集算法的演变和用途
->复制算法
标记-清除 -> 分代收集算法
->标记-整理
标记-清除算法是最基本的算法,而分代收集算法是结合复制算法和标记-整理算法的优点产生
复制算法的优化
有研究表明在 新生代 对象中,只有不到10%对象存活,于是将复制算法中 50% : 50%的内存划分方式变为 90% :10%。故内存空间分为:Survivor区 和 Eden区
内存区 = Survivor + Survivor + Eden
每次使用 Survivor + Eden (占 90%的内存),内存回收后 旧Survivor区 的存活对象复制到新 Survivor 区,即使用另一块 Survivor 和 同一块Eden。
如果某些条件下,存活的对象大于10%,Survivor区放不下这时需要将这些对象放入老年代(分代算法),这就是常说的担保分配(至于担保逻辑自行了解)
分代收集算法
分代算法是将堆内存中 新生代和老年代,根据复制算法和标记-整理算法的优势,回收不同的对象
总结
附录
本地变量表:也称 局部变量表 是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。
新生代: 刚创建出来的对象
老年代: 新生代的基础上,经历过多次GC还没有被回收的对象