JVM内存模型底层机制及垃圾收集机制深度剖析

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指令时,会经历以下步骤:

  1. 类加载检查
  2. 分配内存
  3. 初始化零值
  4. 设置对象头
  5. 执行方法
// 对象创建过程示例
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)

标记-清除算法分为"标记"和"清除"两个阶段:

  1. 标记阶段:标记所有需要回收的对象。
  2. 清除阶段:回收被标记的对象所占用的空间。
// 标记-清除算法的简化实现
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) {
        //
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值