开始改变第一天 JVM的原理到调优(2)

JVM核心技术详解:从类加载到垃圾回收的完整流程

一、从源码到字节码:深入理解Java程序执行

1.1 源码与字节码对应关系

java 复制代码
public class Person {
    private String name = "Jack";
    private int age;
    private final double salary = 100;
    private static String address;
    private final static String hobby = "Programming";
    
    public void say() {
        System.out.println("person say...");
    }
    
    public static int calc(int op1, int op2) {
        op1 = 3;
        int result = op1 + op2;
        Object obj = new Object();  // 新增对象创建示例
        return result;
    }
    
    public static void order() {}
    
    public static void main(String[] args) {
        calc(1, 2);
        order();
    }
}

1.2 字节码指令分析

通过javap -c Person查看字节码:

java 复制代码
public static int calc(int, int);
Code:
   0: iconst_3           // 将常量3压入操作数栈
   1: istore_0           // 将栈顶值存入局部变量op1
   2: iload_0            // 加载局部变量op1到栈
   3: iload_1            // 加载局部变量op2到栈  
   4: iadd               // 执行加法运算
   5: istore_2           // 结果存入局部变量result
   6: new           #12  // 创建Object对象 -> 堆内存
   9: dup                // 复制栈顶引用
   10: invokespecial #13 // 调用构造方法 -> 方法区
   13: astore_3          // 存储引用到局部变量obj
   14: iload_2           // 加载result到栈
   15: ireturn           // 返回结果

public static void main(java.lang.String[]);
Code:
   0: iconst_1           // 常量1入栈
   1: iconst_2           // 常量2入栈
   2: invokestatic  #10  // 调用calc方法 -> 方法区
   5: pop                // 弹出返回值
   6: invokestatic  #11  // 调用order方法
   9: return

二、运行时数据区深度解析

2.1 方法执行时的内存状态

main线程执行calc方法时的栈帧结构:

ini 复制代码
Java虚拟机栈 (Java Virtual Machine Stacks)
├── 栈帧 calc
│   ├── 局部变量表 (Local Variables)
│   │   ├── op1 = 3
│   │   ├── op2 = 2  
│   │   ├── result = 5
│   │   └── obj → [堆内存地址]
│   ├── 操作数栈 (Operand Stacks)
│   │   ├── 10 (临时值示例)
│   │   └── 3
│   ├── 动态链接 (Dynamic Linking) → 指向方法区的方法信息
│   └── 方法返回地址 (Invocation Completion)
└── 栈帧 main
    └── 局部变量表: args

2.2 Java对象内存布局

对象在堆内存中的结构:

java 复制代码
对象头 (Header) [12-16字节]
├── Mark Word [8字节]
│   ├── 哈希码 (HashCode)
│   ├── GC分代年龄 (4bit)
│   ├── 锁状态标志 (2bit)
│   └── 线程持有的锁
├── 类型指针 (Class Pointer) [8字节,压缩后4字节]
└── [数组长度] [4字节,仅数组对象有]

实例数据 (Instance Data)
├── boolean/byte: 1字节
├── short/char: 2字节  
├── int/float: 4字节
├── long/double: 8字节
└── reference: 4-8字节

对齐填充 (Padding)
└── 保证对象大小为8字节的倍数

示例:Person对象内存计算

java 复制代码
private String name;      // 引用: 4字节
private int age;          // int: 4字节  
private final double salary; // double: 8字节
// 对象头: 8(Mark Word) + 4(类指针) = 12字节
// 实例数据: 4 + 4 + 8 = 16字节
// 总大小: 12 + 16 = 28字节 → 对齐到32字节

三、堆内存分区与垃圾回收机制

3.1 堆内存分区设计

堆内存结构:

scss 复制代码
堆 (Heap)
├── 新生代 (Young Generation) [占堆1/3]
│   ├── Eden区 [80%]
│   └── Survivor区 [20%]
│       ├── Survivor0 (S0) [10%]
│       └── Survivor1 (S1) [10%]
└── 老年代 (Old Generation) [占堆2/3]

分配比例:Eden : S0 : S1 = 8 : 1 : 1

3.2 对象分配与晋升机制

对象分配流程:

  1. 新对象创建 → 优先在Eden区分配
  2. Eden空间不足 → 触发Minor GC
  3. GC后存活对象 → 移动到Survivor区
  4. 对象年龄增长 → 每次Minor GC后年龄+1
  5. 年龄阈值(默认15) → 晋升到老年代

GC类型说明:

  • Minor GC:年轻代(Eden + Survivor)垃圾回收,频繁但快速
  • Major GC:老年代垃圾回收
  • Full GC:整个堆(年轻代+老年代)垃圾回收,STW(Stop The World)时间长

3.3 垃圾回收算法核心思想

为什么需要分代收集?

  • 统计规律:大部分对象"朝生夕死"(生命周期短)
  • 优化策略:对新生代频繁GC,对老年代减少GC频率

空间分配担保机制:

  • 当Survivor区空间不足时,老年代提供"分配担保"
  • 确保对象在年轻代GC时能够有地方存放

避免内存碎片:

  • Survivor区采用复制算法,保证S0/S1总有一个为空
  • 解决内存碎片问题,提高内存利用率

四、完整的JVM架构体系

JVM完整组件架构:

scss 复制代码
类文件 (Class File)
↓
类加载器子系统 (ClassLoader Subsystem)
├── 加载 (Loading)
├── 链接 (Linking)
└── 初始化 (Initialization)
↓
运行时数据区 (Runtime Data Areas)
├── 方法区 (Method Area) ← 类信息、常量、静态变量
├── 堆 (Heap) ← 对象实例
├── 虚拟机栈 (JVM Stacks) ← 栈帧
├── 本地方法栈 (Native Method Stacks)
└── 程序计数器 (PC Register)
↓
执行引擎 (Execution Engine)
├── 解释器 (Interpreter)
├── JIT编译器 (Just-In-Time Compiler)
└── 垃圾收集器 (Garbage Collector)
↓
本地方法接口 (Native Method Interface)

五、性能优化关键点

5.1 GC优化策略

  1. 减少Full GC频率

    • 合理设置堆大小:-Xms(初始堆)、-Xmx(最大堆)
    • 调整新生代比例:-XX:NewRatio(新生代/老年代比例)
    • 优化Survivor区:-XX:SurvivorRatio(Eden/Survivor比例)
  2. 监控GC状态

    bash 复制代码
    # 启用GC日志
    -XX:+PrintGCDetails -Xloggc:gc.log
    # 实时监控
    jstat -gc <pid> 1000

5.2 内存参数调优

bash 复制代码
# 堆内存设置
-Xms2g -Xmx2g           # 初始和最大堆内存
-XX:NewRatio=2          # 新生代:老年代=1:2  
-XX:SurvivorRatio=8     # Eden:S0:S1=8:1:1

# GC算法选择
-XX:+UseG1GC            # G1垃圾收集器
-XX:MaxGCPauseMillis=200 # 最大GC停顿时间

六、总结

通过深入分析JVM的完整执行流程,我们可以得出以下关键理解:

  1. 类加载机制确保代码的正确加载和初始化
  2. 运行时数据区各司其职,协同完成程序执行
  3. 堆内存分代设计基于对象生命周期统计规律
  4. 垃圾回收策略平衡性能与内存利用率
  5. 监控与调优是保证应用性能的关键

理解JVM内部机制不仅有助于写出高性能代码,更能帮助我们在遇到性能问题时快速定位和解决。下一节我们将深入探讨各种垃圾回收器的实现原理和适用场景。 怎么设计垃圾回收(下一个文章详细介绍) 垃圾回收机制 (1)确定什么样的对象是垃圾? 引用计数【会有问题,循环引用的问题】 可达性分析【GC Root,由它出发,某个对象Person对象是否可达】

相关推荐
程序员三明治4 小时前
Spring AOP:注解配置与XML配置双实战
java·后端·spring·代理模式·aop·1024程序员节
绝无仅有4 小时前
京东面试题解析:同步方法、线程池、Spring、Dubbo、消息队列、Redis等
后端·面试·github
DKPT5 小时前
JVM直接内存和堆内存比例如何设置?
java·jvm·笔记·学习·spring
绝无仅有5 小时前
京东面试题解析:String与StringBuilder的区别、装箱拆箱、重载与重写总结
后端·面试·github
weixin_436525075 小时前
若依 - idea集成docker一键部署springboot项目(docker-compose)
java·1024程序员节
鼠鼠我捏,要死了捏5 小时前
深入解析Java GC调优:从原理到实战
java·性能优化·gc调优
Live&&learn5 小时前
Tomcat 10和Tomcat 9引入servlet的不同
java·servlet·tomcat
siriuuus5 小时前
JVM 垃圾收集器相关知识总结
java·jvm