引言
Java虚拟机(JVM)的垃圾回收机制(Garbage Collection, GC)是Java编程语言中的一大亮点,它通过自动管理内存,减少了开发者手动管理内存的繁琐和潜在错误。本文将深入探讨JVM的垃圾回收机制,并通过代码样例来具体说明其工作原理。
JVM内存结构
在深入探讨垃圾回收之前,了解JVM的内存结构是必要的。JVM内存结构主要包括以下几个部分:
- 堆(Heap):存储对象实例,是垃圾收集器管理的主要区域。
- 方法区(Method Area):存储类信息、常量、静态变量等。
- 虚拟机栈(VM Stack):存储局部变量表、操作数栈、动态链接、方法出口等信息。
- 本地方法栈(Native Method Stack):为本地方法服务。
- 程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器。
其中,堆和方法区是线程共享的,而虚拟机栈、本地方法栈和程序计数器是线程私有的。
对象的创建与内存分配
创建一个新对象时,JVM会执行以下步骤:
- 类加载检查
- 分配内存
- 初始化零值
- 设置对象头
- 执行方法
内存分配有两种方式:
- 指针碰撞:适用于内存规整的情况,通过移动指针分配内存。
- 空闲列表:适用于内存不规整的情况,维护一个列表记录可用内存块。
以下是一个简化的内存分配过程的代码样例:
class MemoryAllocator {
private long currentMemoryPosition;
// 假设使用指针碰撞
public Object allocate(int size) {
synchronized (this) {
// 确保线程安全
long objectAddress = currentMemoryPosition;
currentMemoryPosition += size;
return new Object(objectAddress);
}
}
}
对象的内存布局
在HotSpot虚拟机中,对象在内存中的布局可以分为三个部分:
- Mark Word:存储对象的哈希码、GC分代年龄、锁状态标志等。
- 类型指针:指向对象的类元数据。
- 实例数据(Instance Data):对象的实际数据。
- 对齐填充(Padding):用于对齐对象内存布局。
垃圾回收算法的底层实现
JVM中的垃圾回收算法主要有以下几种:
-
标记-清除算法
- 标记阶段:通常使用深度优先搜索(DFS)或广度优先搜索(BFS)来遍历对象图。
- 清除阶段:释放未加标记的对象占用的内存。
优点:标记速度与存活对象线性关系,清除速度与内存大小线性关系。 缺点:会产生内存碎片。
以下是一个简化的标记-清除算法的代码样例:
class MarkSweepCollector { Set<Object> liveObjects = new HashSet<>(); void mark(Object root) { if (root == null || liveObjects.contains(root)) return; liveObjects.add(root); for (Object ref : getReferences(root)) { mark(ref); } } void sweep() { for (Object obj : allObjects) { if (!liveObjects.contains(obj)) { free(obj); } } } }
-
复制算法
通常用于新生代垃圾回收,将内存分为Eden空间和两个Survivor空间。
优点:解决了内存碎片问题,只需遍历存活对象进行复制,提高了效率。 缺点:内存使用效率低,每次只能使用一半的内存空间。
以下是一个简化的复制算法的代码样例:
class CopyCollector { void collect(HeapRegion from, HeapRegion to) { for (Object obj : from.getLiveObjects()) { Object copy = copyObject(obj, to); updateReferences(obj, copy); } from.clear(); } }
-
标记-整理算法
通常用于老年代垃圾回收,需要移动对象并更新引用。
优点:内存使用效率高,不会发生碎片化。 缺点:整理阶段需要遍历多次对象,还需要移动对象,效率较低。
以下是一个简化的标记-整理算法的代码样例:
class MarkCompactCollector { void markAndCompact() { mark(); compact(); updateReferences(); } void compact() { Object freePointer = heapStart; for (Object obj : allObjects) { if (isMarked(obj)) { moveObject(obj, freePointer); freePointer += obj.size(); } } } }
垃圾回收器的并发与并行
现代垃圾回收器通常采用并发或并行技术来提高效率:
- 并行(Parallel):多个线程同时执行垃圾回收,但在回收过程中需要暂停应用线程。
- 并发(Concurrent):垃圾回收线程与应用线程同时运行,减少停顿时间。
以下是一个简化的并行垃圾回收器的代码样例:
class ParallelGC {
void parallelMark() {
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
threads.add(new Thread(() -> {
while (hasWorkToDo()) {
Object obj = getNextObject();
mark(obj);
}
}));
}
for (Thread t : threads) t.start();
for (Thread t : threads) t.join();
}
}
增量式垃圾回收与并发标记
- 增量式垃圾回收:将回收过程分解成多个小步骤,减少每次GC的停顿时间。
- 并发标记:在应用程序运行的同时进行对象标记。
- 并发清理:在应用程序运行的同时清理未被标记的对象。
- 预清理:在并发标记阶段结束后,再次扫描新增的或者被修改的对象。
G1垃圾收集器
G1(Garbage-First)是一种面向服务端应用的垃圾收集器,它将堆内存划分为多个大小相等的独立区域(Region)。G1收集器的主要特点是响应时间与吞吐量兼顾,其回收过程分为新生代回收、并发标记和混合收集三个阶段。
示例代码:STW测试
以下是一个测试Stop The World(STW)的代码样例,通过创建对象和打印线程来观察GC导致的停顿时间。
package chapter04.gc;
import lombok.SneakyThrows;
import java.util.LinkedList;
import java.util.List;
public class StopWorldTest {
public static void main(String[] args) {
new PrintThread().start();
new ObjectThread().start();
}
}
class PrintThread extends Thread {
@SneakyThrows
@Override
public void run() {
long last = System.currentTimeMillis();
while (true) {
long now = System.currentTimeMillis();
System.out.println(now - last);
last = now;
Thread.sleep(100);
}
}
}
class ObjectThread extends Thread {
@SneakyThrows
@Override
public void run() {
List<byte[]> bytes = new LinkedList<>();
while (true) {
if (bytes.size() >= 80) {
bytes.clear();
}
bytes.add(new byte[1024 * 1024 * 100]);
Thread.sleep(10);
}
}
}
在运行上述代码之前,可以设置JVM参数来观察GC的行为。
结论
JVM的垃圾回收机制是Java编程语言中的核心特性之一,它通过自动管理内存,极大地减轻了开发者的负担。本文详细介绍了JVM的内存结构、对象的创建与内存分配、对象的内存布局以及垃圾回收算法的底层实现。通过代码样例,读者可以更加直观地理解垃圾回收机制的工作原理。希望本文能对读者深入了解JVM垃圾回收机制有所帮助。