一、JVM 内存区域概述
JVM 将内存划分为多个不同的区域,主要包括:
- 程序计数器:当前线程所执行的字节码的行号指示器。
- Java 虚拟机栈:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
- 本地方法栈:与虚拟机栈类似,只不过是为 Native 方法服务。
- Java 堆:存放对象实例,是垃圾回收的主要区域。
- 方法区:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
二、什么是垃圾回收
垃圾回收(Garbage Collection,GC)是指 JVM 自动管理内存的一种机制。它的主要任务是识别并回收不再被程序使用的内存空间,以避免内存泄漏和提高内存的利用率。
三、如何确定垃圾
- 引用计数法:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加 1;当引用失效时,计数器值就减 1。当计数器为 0 时,就认为这个对象是垃圾,可以被回收。但是这种方法无法解决循环引用的问题。
- 可达性分析算法:通过一系列称为 “GC Roots” 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的,即垃圾对象。
GC Roots 包括以下几种对象:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象。
四、垃圾回收算法
- 标记 - 清除算法
- 原理:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
- 缺点:标记和清除两个过程的效率都不高;会产生大量不连续的内存碎片,可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
- 复制算法
- 原理:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
- 优点:实现简单,运行高效;不会产生内存碎片。
- 缺点:将内存缩小为原来的一半,代价太高。
- 标记 - 整理算法
- 原理:标记过程仍然与 “标记 - 清除算法” 一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
- 优点:避免了 “标记 - 清除算法” 的碎片问题,又不像 “复制算法” 那样需要浪费一半的内存空间。
- 分代收集算法
- 原理:根据对象存活周期的不同将内存划分为几块,一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
- 新生代:通常采用复制算法,因为新生代中的对象大多 “朝生夕死”,存活对象较少,复制成本低。
- 老年代:通常采用标记 - 整理算法或标记 - 清除算法,因为老年代中的对象存活率较高,没有额外的空间对它进行分配担保。
五、垃圾回收器
- Serial 收集器:单线程的收集器,在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。
- ParNew 收集器:Serial 收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为包括 Serial 收集器可用的所有控制参数、收集算法、Stop The World、对象分配规则、回收策略等都与 Serial 收集器完全一样。
- Parallel Scavenge 收集器:新生代收集器,也是使用复制算法的并行多线程收集器,它的目标是达到一个可控制的吞吐量。
- Serial Old 收集器:Serial 收集器的老年代版本,单线程收集器,使用 “标记 - 整理” 算法。
- Parallel Old 收集器:Parallel Scavenge 收集器的老年代版本,使用多线程和 “标记 - 整理” 算法。
- CMS(Concurrent Mark Sweep)收集器:一种以获取最短回收停顿时间为目标的收集器,基于 “标记 - 清除” 算法实现,整个过程分为四个步骤:初始标记、并发标记、重新标记、并发清除。
- G1(Garbage-First)收集器:面向服务端应用的垃圾收集器,将整个 Java 堆划分为多个大小相等的独立区域(Region),跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。
六、总结
JVM 的垃圾回收机制是 Java 语言自动内存管理的核心,了解其工作原理和各种算法、收集器的特点,有助于我们在开发过程中更好地优化程序性能,避免内存泄漏等问题。同时,随着技术的不断发展,垃圾回收机制也在不断演进,我们需要持续关注和学习,以适应不同的应用场景和需求。