JVM内存模型及垃圾收集机制深度剖析
1. JVM内存结构详解
1.1 堆内存(Heap)
堆内存是JVM中最大的一块内存区域,用于存储对象实例。它可以细分为:
1.1.1 新生代(Young Generation)
- Eden空间:大多数新创建的对象首先被分配在Eden空间。
- Survivor空间:包括From Survivor和To Survivor,用于存放经过垃圾回收后存活的对象。
1.1.2 老年代(Old Generation)
存放长期存活的对象和大对象。
// 堆内存分配示例
public class HeapAllocationExample {
public static void main(String[] args) {
// 分配在Eden空间
for (int i = 0; i < 100; i++) {
allocateObject();
}
// 强制进行垃圾回收
System.gc();
}
private static void allocateObject() {
// 创建一个新对象,通常会被分配在Eden空间
Object obj = new Object();
}
}
// 运行时添加JVM参数:-XX:+PrintGCDetails -XX:+UseSerialGC
// 这将使用串行垃圾收集器并打印GC详细信息
1.2 方法区(Method Area)
方法区用于存储已被虚拟机加载的类信息、常量、静态变量等。在JDK 8及以后,方法区被元空间(Metaspace)所取代。
// 方法区(元空间)使用示例
public class MethodAreaExample {
// 静态变量存储在方法区
public static final String CONSTANT = "This is a constant";
// 静态方法的信息也存储在方法区
public static void staticMethod() {
System.out.println("This is a static method");
}
public static void main(String[] args) {
// 访问静态变量和方法
System.out.println(CONSTANT);
staticMethod();
}
}
1.3 虚拟机栈(VM Stack)
虚拟机栈为虚拟机执行Java方法服务。每个方法在执行时都会创建一个栈帧。
// 虚拟机栈示例
public class VMStackExample {
public static void main(String[] args) {
method1();
}
public static void method1() {
int a = 1;
method2();
}
public static void method2() {
int b = 2;
// 方法执行完毕后,对应的栈帧将被弹出
}
}
1.4 本地方法栈(Native Method Stack)
本地方法栈为虚拟机使用到的Native方法服务。
public class NativeMethodStackExample {
// 声明一个native方法
public native void nativeMethod();
static {
// 加载包含native方法实现的库
System.loadLibrary("nativeLib");
}
public static void main(String[] args) {
NativeMethodStackExample example = new NativeMethodStackExample();
example.nativeMethod();
}
}
1.5 程序计数器(Program Counter Register)
程序计数器是一块较小的内存空间,用于指示当前线程所执行的字节码的行号。
2. 对象的创建过程
当虚拟机遇到一条new指令时,会经历以下步骤:
- 类加载检查
- 分配内存
- 初始化零值
- 设置对象头
- 执行方法
// 对象创建过程示例
public class ObjectCreationExample {
public static void main(String[] args) {
// 1. 类加载检查
// 2. 分配内存
// 3. 初始化零值
// 4. 设置对象头
MyObject obj = new MyObject();
// 5. 执行<init>方法
}
}
class MyObject {
private int value;
public MyObject() {
this.value = 42; // 在<init>方法中初始化
}
}
3. 内存分配策略
3.1 对象优先在Eden分配
大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
3.2 大对象直接进入老年代
大对象是指需要大量连续内存空间的Java对象,例如很长的字符串或者数组。
// 大对象直接进入老年代示例
public class LargeObjectAllocationExample {
public static void main(String[] args) {
// 创建一个大对象(假设阈值为3MB)
byte[] largeArray = new byte[4 * 1024 * 1024]; // 4MB
}
}
// 运行时JVM参数:-XX:PretenureSizeThreshold=3145728 -XX:+UseSerialGC
// 这将设置大对象阈值为3MB,并使用串行垃圾收集器
3.3 长期存活的对象将进入老年代
对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认15岁),就会被晋升到老年代中。
4. 垃圾收集算法深度解析
4.1 标记-清除算法(Mark-Sweep)
标记-清除算法分为"标记"和"清除"两个阶段:
- 标记阶段:标记所有需要回收的对象。
- 清除阶段:回收被标记的对象所占用的空间。
// 标记-清除算法的简化实现
public class MarkSweepCollector {
private List<Object> heap = new ArrayList<>();
private Set<Object> markSet = new HashSet<>();
public void collect() {
// 标记阶段
markAllReachableObjects();
// 清除阶段
sweepUnmarkedObjects();
}
private void markAllReachableObjects() {
// 从根对象开始标记
for (Object root : getRootObjects()) {
markObject(root);
}
}
private void markObject(Object obj) {
if (obj == null || markSet.contains(obj)) {
return;
}
// 标记对象
markSet.add(obj);
// 递归标记该对象引用的其他对象
for (Object reference : getReferences(obj)) {
markObject(reference);
}
}
private void sweepUnmarkedObjects() {
Iterator<Object> iterator = heap.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
if (!markSet.contains(obj)) {
// 移除未被标记的对象
iterator.remove();
}
}
// 清理标记集合
markSet.clear();
}
// 辅助方法,在实际实现中需要根据具体情况实现
private List<Object> getRootObjects() { /* ... */ }
private List<Object> getReferences(Object obj) { /* ... */ }
}
4.2 复制算法(Copying)
复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。
// 复制算法的简化实现
public class CopyingCollector {
private List<Object> fromSpace = new ArrayList<>();
private List<Object> toSpace = new ArrayList<>();
public void collect() {
// 从根对象开始复制
for (Object root : getRootObjects()) {
copyObject(root);
}
// 交换 from 和 to 空间
List<Object> temp = fromSpace;
fromSpace = toSpace;
toSpace = temp;
// 清空 to 空间
toSpace.clear();
}
private Object copyObject(Object obj) {
if (obj == null || toSpace.contains(obj)) {
return obj;
}
// 复制对象到 to 空间
toSpace.add(obj);
// 递归复制该对象引用的其他对象
for (Object reference : getReferences(obj)) {
updateReference(obj, reference, copyObject(reference));
}
return obj;
}
// 辅助方法,在实际实现中需要根据具体情况实现
private List<Object> getRootObjects() { /* ... */ }
private List<Object> getReferences(Object obj) { /* ... */ }
private void updateReference(Object obj, Object oldRef, Object newRef) { /* ... */ }
}
4.3 标记-整理算法(Mark-Compact)
标记-整理算法在标记-清除算法的基础上,添加了一个整理的步骤,将所有存活的对象都向一端移动。
// 标记-整理算法的简化实现
public class MarkCompactCollector {
private List<Object> heap = new ArrayList<>();
private Set<Object> markSet = new HashSet<>();
public void collect() {
// 标记阶段
markAllReachableObjects();
// 整理阶段
compactHeap();
}
private void markAllReachableObjects() {
// 从根对象开始标记
for (Object root : getRootObjects()) {
markObject(root);
}
}
private void markObject(Object obj) {
if (obj == null || markSet.contains(obj)) {
return;
}
// 标记对象
markSet.add(obj);
// 递归标记该对象引用的其他对象
for (Object reference : getReferences(obj)) {
markObject(reference);
}
}
private void compactHeap() {
List<Object> newHeap = new ArrayList<>();
for (Object obj : heap) {
if (markSet.contains(obj)) {
newHeap.add(obj);
}
}
heap = newHeap;
markSet.clear();
}
// 辅助方法,在实际实现中需要根据具体情况实现
private List<Object> getRootObjects() { /* ... */ }
private List<Object> getReferences(Object obj) { /* ... */ }
}
5. 垃圾收集器详解
5.1 Serial收集器
Serial收集器是单线程收集器,使用复制算法。
5.2 ParNew收集器
ParNew收集器是Serial收集器的多线程版本。
5.3 Parallel Scavenge收集器
Parallel Scavenge收集器是一个新生代收集器,关注吞吐量。
5.4 CMS(Concurrent Mark Sweep)收集器
CMS收集器是一种以获取最短回收停顿时间为目标的收集器。
// CMS收集器的工作流程示意
public class CMSCollector {
public void collect() {
// 1. 初始标记(STW)
initialMark();
// 2. 并发标记
concurrentMark();
// 3. 重新标记(STW)
remark();
// 4. 并发清除
concurrentSweep();
}
private void initialMark() {
// 标记GC Roots直接关联的对象
}
private void concurrentMark() {
// 并发标记所有可达对象
}
private void remark() {
// 重新标记,处理并发标记阶段遗漏的对象
}
private void concurrentSweep() {
// 并发清除未标记的对象
}
}
5.5 G1(Garbage-First)收集器
G1收集器是一款面向服务端应用的垃圾收集器,具有高吞吐量和低停顿时间的特点。
// G1收集器的工作流程示意
public class G1Collector {
public void collect() {
// 1. 初始标记(STW)
initialMark();
// 2. 并发标记
concurrentMark();
// 3. 最终标记(STW)
finalMark();
// 4. 筛选回收(STW)
evacuationPause();
}
private void initialMark() {
// 标记GC Roots直接关联的对象,并标记survivor区对老年代的引用
}
private void concurrentMark() {
// 并发标记整个堆中的存活对象
}
private void finalMark() {
// 处理并发标记阶段遗漏的对象
}
private void evacuationPause() {
// 选择若干个区域进行回收
}
}
6. JVM调优实践
6.1 内存分配调优
// JVM内存分配参数示例
// -Xms4g -Xmx4g -XX:NewRatio=3 -XX:SurvivorRatio=8
public class MemoryAllocationExample {
public static void main(String[] args) {
// 应用代码
}
}
6.2 垃圾收集器选择
// 使用G1收集器的JVM参数示例
// -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=2M
public class GCCollectorExample {
public static void main(String[] args) {
// 应用代码
}
}
6.3 GC日志分析
// 启用GC日志的JVM参数示例
// -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log
public class GCLogExample {
public static void main(String[] args) {
//