轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

    • 核心

    • 并发

    • 经验

    • JVM

      • JVM体系
        • 一、引言
        • 二、JVM体系结构概述
          • 1、核心组件
          • 2、执行流程
        • 三、类加载器子系统
          • 1、类加载器层次结构
          • 1.1、引导类加载器(Bootstrap Class Loader)
          • 1.2、扩展类加载器(Extension Class Loader)
          • 1.3、应用类加载器(Application Class Loader)
          • 2、双亲委派机制
          • 3、打破双亲委派
        • 四、运行时数据区
          • 1、方法区(Method Area)
          • 1.1、存储内容
          • 1.2、实现演进
          • 2、堆(Heap)
          • 2.1、堆内存结构
          • 2.2、年轻代(Young Generation)
          • 2.3、老年代(Old Generation)
          • 2.4、内存分配策略
          • 3、虚拟机栈(JVM Stack)
          • 3.1、栈帧结构
          • 3.2、异常情况
          • 4、本地方法栈(Native Method Stack)
          • 5、程序计数器(Program Counter Register)
          • 5.1、特点
          • 6、直接内存(Direct Memory)
          • 6.1、特点
          • 6.2、使用场景
          • 6.3、注意事项
        • 五、垃圾回收
          • 1、垃圾回收的基本概念
          • 2、对象存活判定算法
          • 2.1、引用计数算法(Reference Counting)
          • 2.2、可达性分析算法(Reachability Analysis)
          • 3、Java引用类型
          • 3.1、强引用(Strong Reference)
          • 3.2、软引用(Soft Reference)
          • 3.3、弱引用(Weak Reference)
          • 3.4、虚引用(Phantom Reference)
          • 3.5、不可达对象
          • 3.6、方法区的回收
          • 4、垃圾回收算法
          • 4.1、标记-清除算法(Mark-Sweep)
          • 4.2、复制算法(Copying)
          • 4.3、标记-整理算法(Mark-Compact)
          • 4.4、分代收集算法(Generational Collection)
          • 5、垃圾回收器
          • 5.1、经典垃圾回收器
          • a、Serial收集器
          • b、ParNew收集器
          • c、Scavenge收集器
          • d、CMS收集器(Concurrent Mark Sweep)
          • 5.2、新一代垃圾回收器
          • a、G1收集器(Garbage First)
          • b、ZGC(Z Garbage Collector)
          • c、Shenandoah
          • 5.3、最新发展(Java 21+)
          • a、ZGC
          • 5.4、垃圾回收器选择建议
          • 6、调优和性能监控
          • 7、诊断垃圾回收问题
        • 六、JVM的执行引擎
          • 1、解释器
          • 2、Just-In-Time编译器(JIT)
          • 3、HotSpot虚拟机的即时编译
          • 4、优化技术
          • 4.1、方法内联
          • 4.2、逃逸分析
          • 4.3、循环展开
          • 4.4、其他优化技术
          • 4.5、补充:JIT、AOT与JSR 269和Java编译器
        • 七、JMM内存模型
          • 1、原子性、可见性和有序性
          • 2、volatile关键字
          • 3、Happens-Before规则
          • 4、Java中的锁和同步原语
        • 八、Java Native Interface (JNI)
          • 1、JNI的基本概念
          • 2、使用JNI调用本地代码
          • 3、在本地代码中调用Java方法
          • 4、Java和本地代码之间的数据传递
        • 九、字节码
          • 1、什么是 Java 字节码?
          • 2、Java 字节码的结构
          • 3、Java 字节码的使用
          • 4、Java 字节码的优缺点
        • 十、JVM参数
          • 1、内存相关参数
          • 1.1、堆内存设置
          • 1.2、元空间设置(Java 8+)
          • 1.3、栈内存设置
          • 1.4、直接内存设置
          • 2、垃圾回收器参数
          • 3、GC日志参数
          • 4、诊断参数
          • 5、性能优化参数
          • 6、实战配置示例
          • 7、常见问题与参数调整
        • 十一、JVM相关工具
          • 1、命令行工具
          • 1.1、基础工具
          • 1.2、高级工具(Java 9+)
          • 2、可视化工具
          • 2.1、VisualVM
          • 2.2、Flight Recorder (JFR)
          • 2.3、JConsole
          • 3、第三方工具
          • 3.1、Arthas(阿里巴巴)
          • 3.2、MAT (Memory Analyzer)
          • 3.3、GC日志分析工具
          • 4、性能分析实战
          • 5、工具选择建议
        • 十二、总结
          • 1、核心知识回顾
          • 2、JVM发展趋势
          • 2.1、低延迟GC成为主流
          • 2.2、云原生优化
          • 2.3、Valhalla
          • 2.4、Loom
      • 深入理解JIT编译器
      • JVMTI介绍
      • 从Hotspot到GraalVM
    • 企业应用

  • Spring

  • 其他语言

  • 工具

  • 后端
  • Java
  • JVM
轩辕李
2022-02-14
目录

JVM体系

本文深入剖析JVM体系架构,从类加载机制到垃圾回收算法,从内存模型到性能优化,全面解读Java虚拟机的核心原理。

# 一、引言

Java虚拟机(Java Virtual Machine,JVM)是运行Java程序的虚拟计算机,它提供了一个与平台无关的运行环境。JVM是Java实现"一次编写,到处运行"(Write Once, Run Anywhere)的核心技术基础。

理解JVM对于Java开发者来说至关重要,它不仅能帮助我们:

  • 编写出更高效的代码
  • 快速定位和解决生产环境的性能问题
  • 深入理解Java程序的运行机制
  • 进行有效的JVM调优和故障诊断

本文将从架构设计、内存管理、垃圾回收、性能优化等多个维度,全面剖析JVM的核心技术。

# 二、JVM体系结构概述

JVM是一个复杂而精密的系统,其架构设计体现了软件工程的诸多最佳实践。JVM主要由以下核心组件构成:

# 1、核心组件

  • 类加载器子系统(Class Loader Subsystem):负责加载、链接和初始化类文件
  • 运行时数据区(Runtime Data Areas):管理程序运行时的内存结构
  • 执行引擎(Execution Engine):解释或编译字节码为机器指令
  • 垃圾回收器(Garbage Collector):自动管理内存的分配与回收
  • 本地方法接口(JNI):实现Java与本地代码的交互

# 2、执行流程

.java源文件 → javac编译 → .class字节码文件 → 类加载器 → 运行时数据区 → 执行引擎 → 操作系统

接下来,我们将深入探讨每个组件的工作原理和实现细节。

# 三、类加载器子系统

类加载器子系统负责将.class文件加载到JVM中,这个过程包括加载(Loading)、链接(Linking)和初始化(Initialization)三个阶段。

# 1、类加载器层次结构

JVM采用双亲委派模型(Parent Delegation Model)来组织类加载器,形成一个层次化的结构:

# 1.1、引导类加载器(Bootstrap Class Loader)

  • 由C++实现,是JVM的一部分
  • 负责加载$JAVA_HOME/jre/lib下的核心类库
  • 加载java.lang.*、java.util.*等核心包
  • 无法被Java程序直接引用

# 1.2、扩展类加载器(Extension Class Loader)

  • 由sun.misc.Launcher$ExtClassLoader实现
  • 负责加载$JAVA_HOME/jre/lib/ext目录下的扩展类库
  • 加载javax.*等扩展包
  • 父加载器为Bootstrap ClassLoader

# 1.3、应用类加载器(Application Class Loader)

  • 由sun.misc.Launcher$AppClassLoader实现
  • 负责加载用户类路径(classpath)上的类库
  • 是程序中默认的类加载器
  • 父加载器为Extension ClassLoader

# 2、双亲委派机制

双亲委派模型的工作流程:

public Class<?> loadClass(String name) throws ClassNotFoundException {
    // 首先检查类是否已经被加载
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        try {
            // 委派给父加载器加载
            if (parent != null) {
                c = parent.loadClass(name);
            } else {
                // 如果没有父加载器,使用Bootstrap加载器
                c = findBootstrapClassOrNull(name);
            }
        } catch (ClassNotFoundException e) {
            // 父加载器无法加载,尝试自己加载
            c = findClass(name);
        }
    }
    return c;
}

# 3、打破双亲委派

在某些场景下需要打破双亲委派模型:

  • SPI机制:如JDBC驱动加载,使用线程上下文类加载器
  • 热部署:如Tomcat的WebAppClassLoader
  • 模块化系统:如OSGi、Java 9的模块系统

详细示例请参考:Java运行期动态能力

# 四、运行时数据区

运行时数据区负责存储Java程序运行过程中产生的数据。它主要包括以下几个部分:

# 1、方法区(Method Area)

方法区是JVM规范中定义的逻辑概念,用于存储类的元数据信息。不同JVM实现对方法区有不同的实现方式。

# 1.1、存储内容

方法区主要存储以下信息:

  • 类信息:类的完整名称、父类名称、接口列表、访问修饰符等
  • 字段信息:字段名称、类型、修饰符、属性表等
  • 方法信息:方法名称、返回类型、参数列表、字节码、异常表等
  • 运行时常量池:字面量和符号引用
  • 静态变量:类级别的变量
  • JIT编译后的代码缓存

# 1.2、实现演进

永久代(PermGen)- Java 7及之前

  • 使用JVM堆的一部分来实现方法区
  • 固定大小,容易发生OutOfMemoryError: PermGen space
  • 通过-XX:PermSize和-XX:MaxPermSize设置大小

元空间(Metaspace)- Java 8及之后

  • 使用本地内存(Native Memory)实现
  • 动态增长,默认只受系统可用内存限制
  • 通过-XX:MetaspaceSize和-XX:MaxMetaspaceSize控制
  • 解决了永久代的内存溢出问题
// 查看元空间使用情况的示例
public class MetaspaceDemo {
    public static void main(String[] args) {
        List<Class<?>> classes = new ArrayList<>();
        // 动态生成类,观察元空间增长
        for (int i = 0; i < 100000; i++) {
            ClassLoader classLoader = new ClassLoader() {};
            // 动态生成类的逻辑
        }
    }
}

# 2、堆(Heap)

堆是JVM管理的最大内存区域,也是垃圾回收的主要区域,因此也被称为"GC堆"。所有线程共享堆内存,几乎所有的对象实例都在这里分配。

# 2.1、堆内存结构

┌──────────────────────────────────────────────────────────┐
│                         堆内存                           │
├──────────────────────────┬──────────────────────────────┤
│       年轻代(1/3)         │        老年代(2/3)           │
├────────┬────────┬────────┤                              │
│  Eden  │   S0   │   S1   │         Old Generation        │
│  (8)   │   (1)  │   (1)  │                              │
└────────┴────────┴────────┴──────────────────────────────┘

# 2.2、年轻代(Young Generation)

Eden区

  • 新对象的诞生地
  • 占年轻代的8/10(默认)
  • 当Eden区满时触发Minor GC

Survivor区(S0和S1)

  • 两个大小相等的区域,互为From和To
  • 存放Minor GC后的幸存对象
  • 采用复制算法,保证其中一个始终为空

对象晋升机制

// 对象年龄计算示例
public class ObjectAgeDemo {
    public static void main(String[] args) {
        // 1. 新对象在Eden区分配
        Object obj = new Object();
        
        // 2. 经过Minor GC后,存活对象年龄+1,移到Survivor区
        // 3. 年龄达到阈值(默认15)后,晋升到老年代
        // 可通过 -XX:MaxTenuringThreshold 设置
    }
}

# 2.3、老年代(Old Generation)

  • 存放长期存活的对象
  • 大对象直接进入老年代(通过-XX:PretenureSizeThreshold设置)
  • 空间不足时触发Major GC或Full GC
  • 采用标记-清除或标记-整理算法

# 2.4、内存分配策略

  1. 对象优先在Eden分配
  2. 大对象直接进入老年代
  3. 长期存活对象进入老年代
  4. 动态对象年龄判定:Survivor区中相同年龄对象大小总和大于Survivor空间一半时,该年龄及以上对象直接进入老年代
  5. 空间分配担保:老年代为新生代提供分配担保

# 3、虚拟机栈(JVM Stack)

虚拟机栈是线程私有的内存区域,生命周期与线程相同。每个方法执行时都会创建一个栈帧(Stack Frame)。

# 3.1、栈帧结构

public class StackFrameDemo {
    public int calculate(int a, int b) {
        int c = a + b;    // 局部变量表
        return c * 2;     // 操作数栈
    }
}

栈帧包含以下组件:

  • 局部变量表:存放方法参数和局部变量
  • 操作数栈:方法执行时的工作区
  • 动态链接:指向运行时常量池的方法引用
  • 方法返回地址:方法正常退出或异常退出的返回地址

# 3.2、异常情况

  • StackOverflowError:线程请求的栈深度超过虚拟机允许的深度
  • OutOfMemoryError:虚拟机栈动态扩展时无法申请到足够内存

# 4、本地方法栈(Native Method Stack)

本地方法栈为虚拟机使用到的Native方法服务,与虚拟机栈类似,但服务对象不同:

  • 虚拟机栈:执行Java方法(字节码)
  • 本地方法栈:执行Native方法(如C/C++)

HotSpot虚拟机将本地方法栈和虚拟机栈合二为一。

# 5、程序计数器(Program Counter Register)

程序计数器是一块较小的内存空间,它是线程私有的,可以看作当前线程所执行的字节码的行号指示器。

# 5.1、特点

  • JVM中唯一不会发生OutOfMemoryError的内存区域
  • 执行Java方法时,记录正在执行的虚拟机字节码指令地址
  • 执行Native方法时,计数器值为空(Undefined)
// 程序计数器记录的是字节码指令的地址
public void test() {
    int a = 10;      // PC: 0
    int b = 20;      // PC: 2
    int c = a + b;   // PC: 4
}

# 6、直接内存(Direct Memory)

直接内存并不是虚拟机运行时数据区的一部分,也不是JVM规范中定义的内存区域,但这部分内存被频繁使用。

# 6.1、特点

  • 使用Native函数库直接分配堆外内存
  • 通过DirectByteBuffer对象作为这块内存的引用进行操作
  • 避免了Java堆和Native堆之间的数据复制,提高性能
// 直接内存的使用示例
public class DirectMemoryDemo {
    public static void main(String[] args) {
        // 分配直接内存
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024);
        
        // 写入数据
        buffer.put("Hello Direct Memory".getBytes());
        
        // 读取数据
        buffer.flip();
        byte[] bytes = new byte[buffer.remaining()];
        buffer.get(bytes);
    }
}

# 6.2、使用场景

  • NIO:频繁的I/O操作
  • 网络传输:减少数据拷贝
  • 大文件处理:避免堆内存溢出

# 6.3、注意事项

  • 不受JVM GC管理,需要手动释放
  • 受本机总内存限制,可能导致OutOfMemoryError
  • 通过-XX:MaxDirectMemorySize指定大小

# 五、垃圾回收

垃圾回收(Garbage Collection,GC)是JVM自动内存管理的核心机制。它负责自动识别和回收不再使用的对象,从而避免内存泄漏和手动内存管理的复杂性。

# 1、垃圾回收的基本概念

垃圾回收主要关注堆和方法区的内存管理。GC的核心任务包括:

  • 识别垃圾:判断哪些对象是"存活"的,哪些是"死亡"的
  • 回收内存:清理死亡对象占用的内存空间
  • 整理内存:消除内存碎片,提高内存利用率

# 2、对象存活判定算法

# 2.1、引用计数算法(Reference Counting)

为每个对象维护一个引用计数器:

  • 对象被引用时,计数器+1
  • 引用失效时,计数器-1
  • 计数器为0时,对象可被回收

缺点:无法解决循环引用问题

// 循环引用示例
public class ReferenceCountingGC {
    public Object instance = null;
    
    public static void main(String[] args) {
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        
        objA.instance = objB;  // objA引用objB
        objB.instance = objA;  // objB引用objA
        
        objA = null;
        objB = null;
        // 此时两个对象互相引用,引用计数都不为0,但实际已不可达
    }
}

# 2.2、可达性分析算法(Reachability Analysis)

主流JVM采用的算法,通过一系列GC Roots对象作为起点进行搜索:

GC Roots对象包括:

  • 虚拟机栈中引用的对象(局部变量)
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI引用的对象
  • 同步锁(synchronized)持有的对象
  • JVM内部的引用(如基本数据类型对应的Class对象)

# 3、Java引用类型

Java 1.2后扩展了引用类型,不同引用类型在垃圾回收时有不同的处理策略:

# 3.1、强引用(Strong Reference)

// 默认的引用类型
Object obj = new Object();  // 强引用
// 只要强引用存在,对象永远不会被回收

# 3.2、软引用(Soft Reference)

// 内存不足时才会被回收,适合缓存
SoftReference<User> softRef = new SoftReference<>(new User());
User user = softRef.get();  // 可能返回null

# 3.3、弱引用(Weak Reference)

// 下次GC时就会被回收
WeakReference<User> weakRef = new WeakReference<>(new User());
User user = weakRef.get();  // GC后返回null

// 典型应用:WeakHashMap
WeakHashMap<Key, Value> cache = new WeakHashMap<>();

# 3.4、虚引用(Phantom Reference)

// 用于跟踪对象被回收的状态
ReferenceQueue<User> queue = new ReferenceQueue<>();
PhantomReference<User> phantomRef = new PhantomReference<>(new User(), queue);
// phantomRef.get() 永远返回null

引用强度对比:强引用 > 软引用 > 弱引用 > 虚引用

# 3.5、不可达对象

不可达的对象,也并非“非死不可”。宣告对象的死亡,至少经历两次标记过程:

  • 如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。
  • 如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为F-Queue的队列中,并在稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去执行它。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束,这样做的原因是,如果一个对象在finalize()方法中执行缓慢,或者发生了死循环(这种情况下,finalize()方法将永远无法结束),将很可能会导致F-Queue队列中其他对象永久处于等待,甚至导致整个内存回收系统崩溃。finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移除出“即将回收”的集合;如果对象这时候还没有逃脱,那基本上它就真的被回收了。

# 3.6、方法区的回收

堆中,新生代,一次垃圾收集可以回收70%-90%的空间,而永久带的垃圾收集效率远低于此。

永久带的垃圾收集主要两部分:废弃常量和无用的类。

回收废弃常量与回收Java堆中的对象非常相似。

判断无用的类则相对苛刻许多:

  1. 该类所有的实例都已被回收
  2. 加载该类的ClassLoader已被回收
  3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

是否对类进行回收,HotSpot提供了-Xnoclassgc参数,还可以使用-verbose:class以及-XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看类加载和卸载信息。

在旧版的Java虚拟机(JVM)中,方法区使用的是传统的垃圾回收器,例如串行回收器(Serial Collector)或并行回收器(Parallel Collector)。

然而,从Java 8开始,随着永久代的移除,方法区被替换为元数据区(Metaspace),并且不再使用传统的垃圾回收器。元数据区的内存管理是基于垃圾回收器的动态分配和释放机制,而不是通过特定的垃圾回收算法来回收内存。

具体来说,元数据区使用了一种称为"条目列表"(Chunk List)的数据结构来管理元数据的分配和释放。当元数据不再被使用时,它们会被自动释放,无需进行垃圾回收。

# 4、垃圾回收算法

# 4.1、标记-清除算法(Mark-Sweep)

最基础的收集算法,分为"标记"和"清除"两个阶段:

优点:实现简单
缺点:

  • 效率问题:标记和清除效率都不高
  • 空间问题:产生大量内存碎片
标记前:[对象A][对象B][对象C][对象D]
标记后:[对象A*][对象B][对象C*][对象D]  (* 表示标记为垃圾)
清除后:[      ][对象B][      ][对象D]  (产生碎片)

# 4.2、复制算法(Copying)

将内存分为两块,每次只使用一块,GC时将存活对象复制到另一块:

优点:没有内存碎片,实现简单
缺点:内存利用率只有50%

复制前:[对象A][垃圾][对象B][垃圾] | [空闲区域]
复制后:[已清空区域]           | [对象A][对象B]

# 4.3、标记-整理算法(Mark-Compact)

标记后不直接清理,而是将存活对象向一端移动:

优点:没有内存碎片,内存利用率高
缺点:移动对象成本较高

整理前:[对象A][垃圾][对象B][垃圾][对象C]
整理后:[对象A][对象B][对象C][空闲区域]

# 4.4、分代收集算法(Generational Collection)

根据对象存活周期的不同将内存划分为几块:

  • 新生代:大量对象死亡,少量存活 → 使用复制算法
  • 老年代:对象存活率高 → 使用标记-清除或标记-整理算法

# 5、垃圾回收器

HotSpot虚拟机的垃圾回收器发展历程及特点:

# 5.1、经典垃圾回收器

# a、Serial收集器
  • 特点:单线程,Stop-The-World
  • 算法:新生代复制算法,老年代标记-整理
  • 适用场景:单核CPU,小内存应用
  • 启用:-XX:+UseSerialGC
# b、ParNew收集器
  • 特点:Serial的多线程版本
  • 算法:新生代复制算法
  • 适用场景:多核CPU,配合CMS使用
  • 启用:-XX:+UseParNewGC
# c、Scavenge收集器
  • 特点:吞吐量优先,自适应调节
  • 算法:新生代复制算法,老年代标记-整理
  • 适用场景:后台计算任务
  • 启用:-XX:+UseParallelGC
# d、CMS收集器(Concurrent Mark Sweep)
  • 特点:低延迟,并发标记清除
  • 过程:初始标记→并发标记→重新标记→并发清除
  • 缺点:产生内存碎片,CPU敏感
  • 启用:-XX:+UseConcMarkSweepGC(Java 14已废弃)

# 5.2、新一代垃圾回收器

# a、G1收集器(Garbage First)
  • 特点:面向服务端,可预测停顿
  • 创新:Region内存布局,优先回收价值最大的Region
  • 适用场景:大内存,低延迟要求
  • 启用:-XX:+UseG1GC(Java 9后默认)
// G1相关参数配置
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200  // 最大停顿时间目标
-XX:G1HeapRegionSize=32m  // Region大小
# b、ZGC(Z Garbage Collector)
  • 特点:超低延迟,停顿时间不超过10ms
  • 支持:TB级堆内存
  • 技术:着色指针,读屏障
  • 版本:Java 11引入,Java 15正式发布
  • 启用:-XX:+UseZGC
# ZGC配置示例
java -XX:+UseZGC -Xmx16g -Xms16g MyApp
# c、Shenandoah
  • 特点:低延迟,与ZGC目标相似
  • 技术:Brooks Pointers,并发整理
  • 版本:Java 12引入
  • 启用:-XX:+UseShenandoahGC

# 5.3、最新发展(Java 21+)

# a、ZGC
  • Java 21引入分代ZGC
  • 进一步提升性能和吞吐量
  • 启用:-XX:+UseZGC -XX:+ZGenerational

# 5.4、垃圾回收器选择建议

// 小型应用(< 100MB)
-XX:+UseSerialGC

// 吞吐量优先(批处理)
-XX:+UseParallelGC

// 低延迟(< 1秒)
-XX:+UseG1GC

// 超低延迟(< 10ms)
-XX:+UseZGC  // 或 -XX:+UseShenandoahGC

// 默认选择(Java 9+)
// JVM会根据硬件自动选择

# 6、调优和性能监控

在实际应用中,垃圾回收器的选择和配置对程序性能有很大影响。因此,了解如何调优垃圾回收器以满足特定应用需求是非常重要的。以下是一些建议:

  • 选择合适的垃圾回收器:根据应用的特点和需求,选择最合适的垃圾回收器。例如,对于对延迟敏感的应用,可以选择CMS或G1;对于吞吐量优先的应用,可以选择Parallel回收器。
  • 调整堆大小和内存分代:根据应用的内存使用情况,合理调整堆的大小、新生代和老年代的比例。这可以降低垃圾回收的频率和暂停时间,提高程序性能。
  • 监控和诊断:使用JVM提供的工具(如JConsole、VisualVM等)监控程序的内存使用和垃圾回收情况,根据监控结果调整垃圾回收器的配置。

# 7、诊断垃圾回收问题

在某些情况下,应用程序可能会遇到由垃圾回收引起的性能问题。以下是一些建议和技巧,以帮助您诊断和解决垃圾回收相关的问题:

  • 分析垃圾回收日志:通过启用垃圾回收日志记录,您可以收集有关垃圾回收事件的详细信息。这些日志通常包含每次垃圾回收的时间、持续时间、回收的对象数量等信息。通过分析这些日志,您可以确定垃圾回收的频率、暂停时间以及可能的性能瓶颈。
  • 使用性能分析工具:利用性能分析工具(例如:VisualVM、Java Flight Recorder等)可以帮助您分析应用程序的内存使用情况、垃圾回收统计信息以及其他性能指标。这些工具通常提供可视化界面,使您更容易发现和诊断性能问题。
  • 识别内存泄漏:内存泄漏是指应用程序无法释放不再需要的对象,从而导致内存耗尽。内存泄漏可能会导致频繁的垃圾回收和最终导致应用程序崩溃。使用堆分析工具(如:Eclipse Memory Analyzer等)可以帮助您识别和修复内存泄漏问题。
  • 优化对象分配和内存使用:通过优化应用程序的内存使用和对象分配策略,可以减少垃圾回收的负担。例如,使用对象池来重用对象、避免创建大量短暂的临时对象、使用更小的数据结构等。

# 六、JVM的执行引擎

执行引擎(Execution Engine)是 Java 虚拟机(JVM)的一个核心组件,负责执行 Java 字节码。执行引擎将 Java 字节码转换为本地机器指令,从而实现 Java 程序的运行。

# 1、解释器

当 JVM 加载字节码后,解释器将逐条解释并执行字节码指令。解释器是执行引擎的基本组成部分,但解释执行的效率较低,因为每条字节码都需要在运行时解释为机器码。

# 2、Just-In-Time编译器(JIT)

Just-In-Time编译器是Java虚拟机中用于实现即时编译的组件,主要负责将字节码指令编译成本地机器码,并优化代码执行过程中的性能瓶颈。

JIT编译器的优点是可以针对具体的硬件平台和程序运行环境进行优化,提升程序的执行速度和性能,缺点是在编译过程中会消耗一定的系统资源和时间。

# 3、HotSpot虚拟机的即时编译

HotSpot虚拟机是Java平台上应用最广泛的虚拟机之一,是Java的默认虚拟机实现,也是一款高性能、可扩展的虚拟机。在HotSpot虚拟机中,即时编译器采用了C1和C2两个阶段的编译器流水线,分别负责对冷代码和热代码进行编译和优化。

C1 编译器(Client 编译器):C1 编译器主要负责编译冷代码,即执行次数较少的代码。C1 编译器会在编译时进行一些简单的优化,但不会花费太多时间。这样做的目的是在启动速度和执行性能之间找到一个平衡点。编译后的代码会被执行,同时 HotSpot 虚拟机会收集运行时信息,以便进一步优化。

C2 编译器(Server 编译器):C2 编译器主要负责编译热代码,即经常执行的代码。它会使用更复杂、更耗时的优化方法来提高代码的执行效率。这些优化方法可能包括内联、循环展开、常量折叠等。C2 编译器会根据 HotSpot 虚拟机收集到的运行时信息对代码进行优化,以达到最佳的性能。

# 4、优化技术

Java虚拟机提供了一些优化技术,用于提高代码执行的效率和性能。以下是一些常见的优化技术:

# 4.1、方法内联

方法内联是指在编译时将方法调用直接替换为方法体的代码。这样可以减少方法调用的开销和栈帧的创建,提高代码的执行效率。

# 4.2、逃逸分析

逃逸分析是指在编译时分析对象的作用域和生命周期,确定对象是否会被外部引用,以便进行优化。如果对象不会逃逸出方法的作用域,可以将其分配在栈上,而不是在堆上,避免了垃圾回收的开销。

# 4.3、循环展开

循环展开是指在编译时将循环体中的语句复制多次,以减少循环的迭代次数和分支判断,提高代码的执行效率。但循环展开也会增加代码的体积,可能会影响缓存的命中率和分支预测的准确性。

# 4.4、其他优化技术

Java虚拟机还提供了其他一些优化技术,如栈上分配、标量替换、去除冗余操作、类加载优化等。这些技术都是为了提高代码执行的效率和性能,减少垃圾回收的开销和内存的占用。

# 4.5、补充:JIT、AOT与JSR 269和Java编译器

JIT和AOT都是属于虚拟机的一部分,而JSR 269属于Java编译器(javac)的一部分。

Java编译器(Java Compiler)不是JVM的一部分,它是独立于JVM的一个工具,用于将Java源代码编译成Java字节码文件,以供JVM执行。

在Java开发中,Java编译器是将Java源代码转化为Java字节码文件的必备工具,它将Java源代码编译成平台无关的字节码指令,以便在任何支持Java虚拟机的平台上运行。Java编译器的主要作用是检查Java源代码的语法和语义,并将其转化为字节码指令,同时还可以对Java源代码进行优化和压缩,以提高程序的执行效率和性能。

Java编译器通常包括两个主要的工具:javac和javadoc。其中,javac用于编译Java源代码,生成对应的字节码文件;javadoc用于生成Java源代码的文档注释。

在JVM中,编译后的Java字节码文件是由JVM解释执行或者即时编译执行的。因此,Java编译器和JVM是紧密相关的两个组件,但并不是JVM的一部分。

# 七、JMM内存模型

JMM定义了一套规范,描述了在Java程序中线程如何访问内存。JMM规范中涉及到的概念有:

  • 主内存(Main Memory):Java虚拟机所管理的内存,是所有线程共享的内存区域。
  • 工作内存(Working Memory):每个线程独立的内存区域,存储线程运行过程中需要读写的变量拷贝。
  • 内存间交互操作:Java程序中使用volatile、synchronized、final、Lock等关键字进行内存间的交互操作。

# 1、原子性、可见性和有序性

JMM规范中定义了原子性、可见性和有序性三个概念。它们分别对应了在Java程序中线程对内存访问时可能出现的问题。

  • 原子性:指一个操作是不可中断的整体,要么全部执行成功,要么全部执行失败。
  • 可见性:指当一个线程修改了共享变量的值后,其他线程能够立即看到这个修改。
  • 有序性:指程序执行的顺序,JMM规定了一些约束条件,保证程序执行的顺序是正确的。

# 2、volatile关键字

volatile关键字可以保证共享变量的可见性和一定程度上的有序性,但并不能保证原子性。当一个变量被volatile修饰时,所有线程都会从主内存中读取这个变量的值,而不是从自己的工作内存中读取。当一个线程修改了共享变量的值后,这个值会立即写回到主内存,而不是在某个时刻才写回。

# 3、Happens-Before规则

Happens-Before规则是JMM中一个重要的概念,用于描述在多线程并发访问内存时的时序关系。它规定了一些原则,确保程序执行的结果是正确的。Happens-Before规则可以帮助我们理解Java程序中内存访问的行为和规则,从而更好地编写高效、正确和线程安全的Java程序。

Happens-Before 规则包括以下几个方面:

  • 程序顺序规则:在一个线程内,一个操作(如变量赋值、方法调用等)按程序顺序发生在另一个操作之前,那么这两个操作满足 Happens-Before 关系。换句话说,在同一个线程中,代码的执行顺序遵循书写顺序。
  • 监视器锁规则:对一个锁的解锁操作 Happens-Before 该锁的后续加锁操作。当线程 A 释放一个锁,然后线程 B 获取该锁时,线程 A 在释放锁之前所进行的所有操作对线程 B 都是可见的。
  • volatile 变量规则:对一个 volatile 变量的写操作 Happens-Before 该变量的后续读操作。当线程 A 写入一个 volatile 变量,然后线程 B 读取该变量时,线程 A 在写入该变量之前所进行的所有操作对线程 B 都是可见的。
  • 线程启动规则:线程 A 启动线程 B 时,线程 A 对线程 B 的启动操作 Happens-Before 线程 B 中的任何操作。换句话说,当线程 A 启动线程 B 时,线程 A 在启动线程 B 之前所进行的所有操作对线程 B 都是可见的。
  • 线程终止规则:线程 A 的终止操作 Happens-Before 任何线程 B 检测到线程 A 已经终止(通过 Thread.join() 方法或其他方式)。当线程 A 结束时,线程 A 在结束之前所进行的所有操作对线程 B 都是可见的,前提是线程 B 检测到了线程 A 的终止。
  • 线程中断规则:线程 A 对线程 B 的中断操作 Happens-Before 线程 B 发现中断事件(通过调用 Thread.interrupted() 方法或其他方式)。
  • 对象构造函数规则:对象构造函数的执行 Happens-Before 该对象的 finalizer 方法。在构造对象时,构造函数中所进行的所有操作对 finalizer 方法都是可见的。

# 4、Java中的锁和同步原语

Java中提供了多种锁和同步原语,用于保证线程安全。其中最常用的有synchronized关键字和ReentrantLock类。synchronized关键字是Java中最基本的同步机制,它采用的是互斥锁机制,确保同一时刻只有一个线程可以进入同步代码块。而ReentrantLock是一个可重入的互斥锁,相比synchronized具有更高的灵活性和可定制性。

除了基本的锁和同步机制外,Java中还提供了一些高级的同步工具,如Semaphore、CountDownLatch、CyclicBarrier等,它们可以帮助我们更好地管理线程和协调线程之间的交互行为。

# 八、Java Native Interface (JNI)

Java Native Interface (JNI) 是Java平台提供的一种机制,用于在Java代码和本地代码(如C/C++代码)之间进行交互。通过JNI,Java应用程序可以调用本地代码提供的功能,也可以将Java对象传递给本地代码进行处理。

# 1、JNI的基本概念

JNI提供了一些标准的API和约定,用于描述Java和本地代码之间的交互。JNI的基本概念如下:

  • Native Method:在Java类中声明的本地方法,使用native关键字修饰。

  • JNIEnv:JNI环境接口,用于调用JNI提供的各种函数。

  • jclass、jobject、jarray等:JNI定义的一系列类型,用于表示Java中的类、对象和数组等数据结构。

  • jfieldID、jmethodID等:用于标识Java中的字段和方法等成员的唯一ID。

# 2、使用JNI调用本地代码

在Java中调用本地代码,需要经过以下步骤:

  1. 编写本地代码,并生成本地库文件(如动态链接库.so文件)。

  2. 在Java类中声明native方法,以便在Java中调用本地代码。

  3. 在Java代码中使用System.loadLibrary()函数加载本地库文件。

  4. 在Java代码中调用native方法,触发JNI机制将调用传递到本地代码中。

# 3、在本地代码中调用Java方法

在本地代码中调用Java方法,需要经过以下步骤:

  1. 获取JNIEnv接口指针,以便在本地代码中调用JNI提供的函数。

  2. 使用FindClass()函数查找Java类,使用GetMethodID()函数获取Java方法的ID。

  3. 使用CallXXXMethod()函数调用Java方法,其中XXX可以是Void、Boolean、Byte、Char、Short、Int、Long、Float或Double等基本类型。

# 4、Java和本地代码之间的数据传递

在Java和本地代码之间传递数据,需要经过以下步骤:

  1. 在Java中创建jobject或jarray对象,用于表示数据结构。

  2. 在本地代码中使用GetXXXArrayElements()函数获取数组元素或GetXXXField()函数获取对象字段的值。

  3. 在本地代码中使用SetXXXArrayElements()函数设置数组元素或SetXXXField()函数设置对象字段的值。

  4. 在本地代码中使用NewXXXArray()函数创建数组对象或NewObject()函数创建Java对象。

综上所述,JNI提供了Java和本地代码之间进行交互的一种机制,可以帮助Java应用程序获得更高的灵活性和性能。但是,由于涉及到跨语言交互,JNI的使用也需要非常小心谨慎,避免出现内存泄漏、数据类型不匹配等问题。

# 九、字节码

Java 之所以如此受欢迎,一方面是因为它具有跨平台性,另一方面则是因为它使用了一种称为 Java 字节码的中间语言。

# 1、什么是 Java 字节码?

Java 字节码是一种中间语言,是 Java 源代码编译后得到的二进制代码,也被称为 JVM 代码。Java 虚拟机可以解释并执行这些字节码。由于字节码是中间语言,所以它具有跨平台性,可以在不同的操作系统和硬件平台上运行。

Java 字节码可以通过使用 javac 命令将 Java 源代码编译成 .class 文件得到。.class 文件包含了 Java 字节码以及一些其他的元数据信息,例如类名、方法名等。Java 虚拟机可以读取 .class 文件,并将其中的字节码解释成机器码来执行程序。

# 2、Java 字节码的结构

Java 字节码是由一系列指令组成的,每个指令都对应着一些操作。

指令是 Java 虚拟机(JVM)执行 Java 字节码时所遵循的指令集。这些指令控制 JVM 如何处理数据、执行操作以及调用方法等。按用途分类,字节码操作指令大致分为9类。以下是这9类指令的详细介绍:

  1. 加载指令(Load):这类指令用于将数据从内存(如局部变量表或类字段)加载到操作数栈中。例如,iload 指令从局部变量表加载 int 类型数据到操作数栈,aload 指令从局部变量表加载引用类型数据到操作数栈。
  2. 存储指令(Store):这类指令用于将数据从操作数栈存储到内存(如局部变量表或类字段)。例如,istore 指令将 int 类型数据从操作数栈存储到局部变量表,astore 指令将引用类型数据从操作数栈存储到局部变量表。
  3. 栈操作指令(Stack):这类指令用于操作数栈的管理,如压栈、弹栈、交换栈顶元素等。例如,pop 指令用于弹出操作数栈顶的元素,dup 指令用于复制操作数栈顶的元素。
  4. 算术指令(Arithmetic):这类指令用于执行基本的算术运算,如加法、减法、乘法、除法等。例如,iadd 指令用于执行 int 类型的加法运算,ddiv 指令用于执行 double 类型的除法运算。
  5. 类型转换指令(Type Conversion):这类指令用于在不同数据类型之间进行转换。例如,i2f 指令用于将 int 类型数据转换为 float 类型数据,l2d 指令用于将 long 类型数据转换为 double 类型数据。
  6. 对象操作指令(Object Manipulation):这类指令用于操作对象,如创建对象、访问字段、调用方法等。例如,new 指令用于创建一个新对象,getfield 指令用于访问对象的实例字段,invokevirtual 指令用于调用对象的虚方法。
  7. 控制转移指令(Control Transfer):这类指令用于控制程序的执行流程,如条件跳转、无条件跳转、循环等。例如,ifeq 指令用于在条件为真时跳转到指定位置,goto 指令用于无条件跳转到指定位置。
  8. 异常处理指令(Exception Handling):这类指令用于异常的处理和抛出。例如,athrow 指令用于抛出异常,jsr 和 ret 指令用于跳转到异常处理程序并返回。
  9. 同步指令(Synchronization):这类指令用于实现线程同步,如进入或退出监视器(monitor)。例如,monitorenter 指令用于获取对象的监视器,monitorexit 指令用于释放对象的监视器。

# 3、Java 字节码的使用

Java 字节码可以通过使用反编译工具来查看,例如 javap 命令。使用 javap 命令可以查看 .class 文件中包含的字节码信息,例如方法名、方法参数、返回类型以及字节码指令等。

Java 字节码也可以通过 ASM(Java 字节码操作框架)这样的字节码操作框架来生成和修改。ASM 提供了一组 API,用于操作字节码,并提供了一些工具类,例如 ClassWriter、MethodVisitor 等,用于生成和修改字节码。通过使用 ASM,开发人员可以在不改变 Java 语言语法的情况下,生成高效的字节码。

Java 字节码还可以用于进行代码分析和优化。在进行代码分析时,可以使用字节码工具来分析代码中的指令、变量和方法等信息,以便进行代码调试和性能优化。在进行代码优化时,可以通过改进字节码的生成方式来提高程序的性能。

# 4、Java 字节码的优缺点

Java 字节码的主要优点是跨平台性。由于字节码是一种中间语言,可以在不同的操作系统和硬件平台上运行。这样可以使开发人员只需要编写一次代码,就可以在不同的平台上运行。

Java 字节码的另一个优点是安全性。Java 字节码可以通过 Java 虚拟机的安全机制来执行,这样可以有效地防止一些安全漏洞,例如缓冲区溢出等。

Java 字节码的主要缺点是性能。由于 Java 字节码需要被解释成机器码来执行,所以它的执行速度相对较慢。这个问题可以通过 JIT(Just-In-Time)编译器来解决,JIT 编译器可以将字节码编译成本地机器码来执行,这样可以大大提高程序的执行速度。

另外,由于 Java 字节码是一种中间语言,所以它的可读性相对较差。对于开发人员来说,需要掌握一定的字节码知识才能够有效地分析和优化字节码。

# 十、JVM参数

JVM参数分为标准参数、非标准参数(-X)和高级参数(-XX):

# 1、内存相关参数

# 1.1、堆内存设置

-Xms<size>              # 初始堆大小,如 -Xms2g
-Xmx<size>              # 最大堆大小,如 -Xmx4g
-Xmn<size>              # 年轻代大小
-XX:NewRatio=<n>        # 老年代/年轻代比例,默认2
-XX:SurvivorRatio=<n>   # Eden/Survivor比例,默认8

# 1.2、元空间设置(Java 8+)

-XX:MetaspaceSize=<size>       # 元空间初始大小
-XX:MaxMetaspaceSize=<size>    # 元空间最大大小

# 1.3、栈内存设置

-Xss<size>              # 线程栈大小,如 -Xss1m

# 1.4、直接内存设置

-XX:MaxDirectMemorySize=<size>  # 最大直接内存

# 2、垃圾回收器参数

# 选择垃圾回收器
-XX:+UseSerialGC        # Serial收集器
-XX:+UseParallelGC      # Parallel收集器
-XX:+UseG1GC           # G1收集器
-XX:+UseZGC            # ZGC收集器(Java 11+)
-XX:+UseShenandoahGC   # Shenandoah收集器

# G1特定参数
-XX:MaxGCPauseMillis=<n>       # 最大停顿时间目标
-XX:G1HeapRegionSize=<size>    # Region大小

# ZGC特定参数(Java 15+)
-XX:ZCollectionInterval=<seconds>  # GC间隔
-XX:+ZGenerational              # 启用分代ZGC(Java 21+)

# 3、GC日志参数

# Java 9之前
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps

# Java 9+统一日志
-Xlog:gc*:file=gc.log:time,uptime,level,tags
-Xlog:gc+heap=debug:file=heap.log

# 4、诊断参数

# OOM时生成堆转储
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=<path>

# 打印JVM参数
-XX:+PrintFlagsFinal
-XX:+PrintCommandLineFlags

# JIT编译
-XX:+PrintCompilation
-XX:+UnlockDiagnosticVMOptions

# 5、性能优化参数

# 字符串去重(G1)
-XX:+UseStringDeduplication

# TLAB(线程本地分配缓冲)
-XX:+UseTLAB
-XX:TLABSize=<size>

# 大页内存
-XX:+UseLargePages
-XX:LargePageSizeInBytes=<size>

# 6、实战配置示例

# 高并发Web应用(G1)
java -Xms4g -Xmx4g \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:+ParallelRefProcEnabled \
     -Xlog:gc*:file=gc.log \
     MyApp

# 大内存低延迟应用(ZGC)
java -Xms16g -Xmx16g \
     -XX:+UseZGC \
     -XX:+ZGenerational \
     -XX:ConcGCThreads=4 \
     MyApp

# 批处理应用(Parallel)
java -Xms8g -Xmx8g \
     -XX:+UseParallelGC \
     -XX:ParallelGCThreads=8 \
     MyApp

# 7、常见问题与参数调整

问题 可能原因 建议参数调整
OutOfMemoryError: Java heap space 堆内存不足 增大-Xmx
OutOfMemoryError: Metaspace 元空间不足 增大-XX:MaxMetaspaceSize
StackOverflowError 栈深度过大 增大-Xss
GC频繁 堆内存过小 增大-Xms和-Xmx
GC停顿时间长 堆内存过大或GC不合适 使用G1或ZGC

# 十一、JVM相关工具

# 1、命令行工具

# 1.1、基础工具

# jps - 查看Java进程
jps -l           # 显示完整类名
jps -v           # 显示JVM参数

# jinfo - 查看/修改JVM参数
jinfo -flags <pid>      # 查看所有JVM参数
jinfo -flag MaxHeapSize <pid>  # 查看具体参数

# jstat - 监控JVM统计信息
jstat -gc <pid> 1000    # 每秒输出GC信息
jstat -gcutil <pid>     # 查看GC使用率
jstat -class <pid>      # 查看类加载信息

# jstack - 线程堆栈分析
jstack <pid>            # 打印线程堆栈
jstack -l <pid>         # 包含锁信息

# jmap - 内存映像工具
jmap -heap <pid>        # 查看堆配置和使用
jmap -histo <pid>       # 查看对象统计
jmap -dump:format=b,file=heap.bin <pid>  # 生成堆转储

# 1.2、高级工具(Java 9+)

# jhsdb - 调试工具
jhsdb jmap --heap --pid <pid>
jhsdb jstack --locks --pid <pid>

# jcmd - 多功能诊断命令
jcmd <pid> VM.version
jcmd <pid> GC.run
jcmd <pid> Thread.print
jcmd <pid> VM.native_memory

# 2、可视化工具

# 2.1、VisualVM

  • 免费的多合一故障处理工具
  • 支持性能分析、内存分析、线程分析
  • 可以安装各种插件扩展功能

# 2.2、Flight Recorder (JFR)

# 启动JFR记录
jcmd <pid> JFR.start duration=60s filename=recording.jfr

# 分析JFR文件
jfr print recording.jfr
jfr summary recording.jfr

# 2.3、JConsole

  • JDK自带的监控工具
  • 实时监控内存、线程、类加载、MBean

# 3、第三方工具

# 3.1、Arthas(阿里巴巴)

# 安装启动
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

# 常用命令
dashboard        # 实时数据面板
thread          # 查看线程信息
trace           # 方法内部调用路径
watch           # 观察方法返回值
profiler        # 性能分析

特点:

  • 无需重启应用即可诊断
  • 支持动态追踪方法调用
  • 可以热更新代码
  • Web Console界面

# 3.2、MAT (Memory Analyzer)

  • 专业的Java堆内存分析工具
  • 可以分析大型堆转储文件
  • 自动检测内存泄漏

# 3.3、GC日志分析工具

  • GCEasy:https://gceasy.io/ (opens new window)
  • GCViewer:开源的GC日志可视化工具
  • FastThread:线程分析工具

# 4、性能分析实战

# 1. 发现高CPU占用进程
top -H -p <pid>

# 2. 将线程ID转换为16进制
printf "%x\n" <thread_id>

# 3. 查找对应线程堆栈
jstack <pid> | grep -A 20 <hex_thread_id>

# 4. 分析内存泄漏
jmap -dump:live,format=b,file=heap.bin <pid>
# 使用MAT或VisualVM分析heap.bin

# 5. 实时监控GC
jstat -gcutil <pid> 1000 10

# 5、工具选择建议

场景 推荐工具
快速诊断 Arthas
内存泄漏分析 Eclipse MAT
性能剖析 JFR + JMC
实时监控 VisualVM
GC优化 GCEasy
线程问题 jstack + FastThread

# 十二、总结

# 1、核心知识回顾

通过本文的深入剖析,我们系统地了解了JVM的核心技术体系:

  • 类加载机制:双亲委派模型保证了Java核心类库的安全性
  • 内存模型:分代收集思想优化了垃圾回收效率
  • 垃圾回收:从Serial到ZGC,GC技术不断演进以适应不同场景
  • 执行引擎:JIT编译技术让Java性能接近原生代码
  • 诊断工具:丰富的工具链支持线上问题快速定位

# 2、JVM发展趋势

# 2.1、低延迟GC成为主流

  • ZGC和Shenandoah将停顿时间控制在10ms以内
  • 分代ZGC(Java 21)进一步提升吞吐量
  • 未来GC将向亚毫秒级停顿发展

# 2.2、云原生优化

  • 启动速度优化:CDS(Class Data Sharing)、AOT编译
  • 内存占用优化:更智能的内存管理策略
  • 容器感知:自动适配容器资源限制

# 2.3、Valhalla

  • 值类型(Value Types):减少对象头开销
  • 泛型特化(Generic Specialization):消除装箱开销

# 2.4、Loom

  • 虚拟线程(Virtual Threads):轻量级并发模型
  • 结构化并发:简化并发编程模型

JVM是一个持续演进的技术体系,深入理解JVM不仅能帮助我们写出更好的代码,更能让我们在遇到问题时快速定位和解决。希望本文能为你的JVM学习之旅提供帮助。

祝你变得更强!

编辑 (opens new window)
上次更新: 2025/08/15
Java 11升级到Java 17及Spring Boot 3迁移实战指南
深入理解JIT编译器

← Java 11升级到Java 17及Spring Boot 3迁移实战指南 深入理解JIT编译器→

最近更新
01
AI时代的编程心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code实战之供应商切换工具
08-18
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式