黑马程序员JVM基础学习笔记
本文为该视频基础篇的学习笔记: 黑马程序员JVM虚拟机入门到实战全套视频教程,java大厂面试必会的jvm一套搞定(丰富的实战案例及最热面试题)
JVM基础学习笔记
一、JVM核心概念
1.1 什么是JVM
JVM(Java Virtual Machine,Java虚拟机)是运行Java字节码的虚拟计算机,是连接Java源代码与底层操作系统的中间层,其核心能力包括:
- 解释运行:实时将字节码指令翻译为机器码并执行,实现“一次编写,到处运行”的跨平台特性。
- 内存管理:自动为对象、方法分配内存,并通过垃圾回收机制回收不再使用的对象,减少手动内存管理的风险。
- 即时编译(JIT):对频繁执行的热点代码进行编译优化,将字节码直接转换为机器码缓存,提升运行性能。
1.2 JIT即时编译的演进
- JDK1.1引入JIT:解决了解释执行效率低的问题。原理是将多次运行的代码(热点代码)编译为机器码并保存在内存中,后续调用时直接从内存获取机器码执行,避免重复解释。
- 现代JIT优化:不仅缓存编译结果,还会通过方法内联、循环展开等优化手段进一步提升代码执行效率,是JVM性能优化的核心机制之一。
1.3 常见JVM实现
不同厂商基于JVM规范实现了多种虚拟机,适用于不同场景:
- HotSpot:Oracle JDK和OpenJDK的默认虚拟机,采用解释执行与JIT编译混合模式,性能优异,应用最广泛。
- GraalVM:Oracle推出的跨语言虚拟机,支持Java、JavaScript、Python等多种语言,同时提供AOT(提前编译)能力,适合微服务与云原生场景。
- Dragonwell JDK:阿里巴巴基于OpenJDK定制的虚拟机,针对电商高并发、大数据场景优化,增强了稳定性和性能。
二、JVM组成结构
JVM的核心组件包括类加载器、运行时数据区、执行引擎和本地接口,协同完成字节码的加载、执行与内存管理。
2.1 类加载器(ClassLoader)
负责将字节码文件(.class)加载到内存,是连接字节码与JVM的桥梁。
2.2 运行时数据区
JVM在运行过程中管理的内存区域,分为线程私有和线程共享两类(详见“四、运行时数据区”)。
2.3 执行引擎
- 解释器:实时翻译字节码为机器码,启动快但执行效率低。
- JIT编译器:对热点代码编译优化,执行效率高但启动时有额外开销。
- 垃圾回收器:回收线程共享区域中不再使用的对象,释放内存。
2.4 本地接口(Native Interface)
用于调用非Java语言实现的本地方法(如C/C++代码),通过JNI(Java Native Interface)实现Java与底层系统的交互。
三、字节码与类加载机制
3.1 字节码文件的组成
字节码是源代码编译后的二进制文件(.class),是JVM的“机器语言”,其结构包括:
- 基础信息
- 魔数:文件头4字节固定为
cafebabe
,用于校验.class文件合法性。 - JDK版本号:主版本号=JDK版本+44(如JDK8对应主版本号52),用于保证兼容性。
- 统计计数与索引:记录常量池、字段、方法等组件的数量与位置。
- 魔数:文件头4字节固定为
- 常量池
- 存储字符串常量、类/接口名、字段名等共享数据,避免重复定义以节省空间。
- 字段表
- 描述类的成员变量(包括静态变量和实例变量),包含访问修饰符、数据类型、名称等信息。
- 方法表
- 描述类的方法,包含方法名、参数列表、返回值类型,以及方法体对应的字节码指令(如
istore
、iload
、iconst
等)。 - 每个方法对应一个栈帧,包含局部变量表、操作数栈等运行时数据(详见“栈帧的组成”)。
- 描述类的方法,包含方法名、参数列表、返回值类型,以及方法体对应的字节码指令(如
- 属性表
- 存储额外信息,如代码行号映射、异常处理表、注解等。
3.2 字节码常用工具
- javap:JDK自带命令行工具,通过
javap -v 类名.class
可查看字节码详细信息(包括常量池、方法指令等),适合服务器环境使用。 - jclasslib:IDEA插件,可视化展示字节码结构,支持查看常量池、方法指令、属性等,适合开发阶段分析。
- Arthas:阿里开源的诊断工具,提供
dashboard
(监控面板)、jad
(反编译字节码)、redefine
(类热部署)等功能,可在线排查问题。
3.3 类的生命周期
类从加载到卸载的完整过程包括5个阶段:
3.3.1 加载
- 类加载器根据类的全限定名(如
java.lang.String
),从文件、网络等渠道获取字节码二进制流。 - 将字节码信息存入方法区,生成C++实现的
InstanceKlass
对象(存储类的元信息,不可直接访问)。 - 在堆中生成对应的
java.lang.Class
对象(Java层可访问,用于反射操作)。- JDK8后,静态字段从方法区移至堆中的
Class
对象,增强内存管理灵活性。
- JDK8后,静态字段从方法区移至堆中的
3.3.2 连接
- 验证:校验字节码是否符合JVM规范(如魔数、版本号、指令合法性),避免恶意代码执行。
- 准备:为静态变量分配内存并赋默认值(如
int
默认0,Object
默认null
);final
静态常量直接赋初始值(如public static final int a=10
在此时赋值)。 - 解析:将常量池中的符号引用(如类名、方法名)替换为内存中的直接引用(地址),建立关联。
3.3.3 初始化
- 执行静态代码块和静态变量的赋值语句,对应字节码中的
clinit
方法(由编译器自动生成)。 clinit
方法的执行顺序与代码编写顺序一致,且仅执行一次(多线程下同步执行,避免重复初始化)。
3.3.4 使用
- 类的实例化(
new
对象)、调用静态方法/变量等操作。
3.3.5 卸载
- 类被垃圾回收器回收,需同时满足:
- 该类及子类的所有实例已被回收。
- 加载该类的类加载器已被回收。
- 该类的
Class
对象无任何引用。
3.4 类初始化的触发条件
以下情况会触发类的初始化(执行clinit
方法):
- 访问类的非
final
静态变量或静态方法(如A.staticField
、A.staticMethod()
)。 - 调用
Class.forName("全限定名")
主动加载类。 - 实例化类(
new A()
)。 - 执行该类的
main
方法。 - 子类初始化前,其父类必先初始化(接口除外,父接口初始化不依赖子接口)。
final
变量的赋值需要计算(如public static final int a = new Random().nextInt()
),而非编译期常量。
3.5 不触发类初始化的情况
- 类无静态代码块和静态变量赋值语句(
clinit
方法不存在)。 - 仅声明静态变量但未赋值(如
public static int a;
,准备阶段已赋默认值)。 final
变量为编译期常量(如public static final int a=10
,准备阶段已赋值)。- 直接访问父类静态变量(如
SubClass.parentStaticField
,仅初始化父类,子类不初始化)。 - 创建数组(如
A[] arr = new A[10]
,仅初始化数组类,不初始化A
类)。
3.6 类加载器的分类
类加载器负责加载类,分为虚拟机底层实现和Java代码实现两类:
JDK8及之前的类加载器
- 启动类加载器(Bootstrap ClassLoader)
- 虚拟机底层C++实现,加载
JRE/lib
下的核心类(如java.lang.String
)。 - 无法通过Java代码获取(
Class.getClassLoader()
返回null
)。 - 扩展加载路径:通过
-Xbootclasspath/a:jar路径
添加用户jar包。
- 虚拟机底层C++实现,加载
- 扩展类加载器(Extension ClassLoader)
- Java代码实现(
sun.misc.Launcher$ExtClassLoader
),加载JRE/lib/ext
下的扩展类。 - 父加载器为
null
(逻辑上委托给启动类加载器)。 - 扩展加载路径:通过
-Djava.ext.dirs=原目录;用户目录
指定(会覆盖默认目录)。
- Java代码实现(
- 应用程序类加载器(Application ClassLoader)
- Java代码实现(
sun.misc.Launcher$AppClassLoader
),加载classpath下的自定义类和第三方jar包。 - 父加载器为扩展类加载器,是默认的类加载器(
Thread.currentThread().getContextClassLoader()
默认返回此加载器)。
- Java代码实现(
JDK9及之后的类加载器(模块化)
JDK9引入模块系统后,类加载器结构调整:
- 启动类加载器:加载核心模块(如
java.base
)。 - 平台类加载器(Platform ClassLoader):替代扩展类加载器,加载非核心但重要的平台模块(如
java.sql
)。 - 系统类加载器(System ClassLoader):替代应用程序类加载器,加载类路径和模块路径中的类。
3.7 双亲委派机制
类加载器的核心机制,保证类加载的安全性和唯一性。
作用
- 安全隔离:防止自定义类替换核心类(如自定义
java.lang.String
,会被启动类加载器优先加载核心类而失效)。 - 避免重复加载:同一类被多个类加载器加载会视为不同类,双亲委派确保类只被加载一次。
机制
- 类加载器通过
parent
属性定义委派关系(非继承关系),形成层级:启动类加载器 → 扩展类加载器 → 应用程序类加载器。 - 加载流程:
- 自底向上检查:子类加载器先委托父类加载器检查该类是否已加载。
- 自顶向下尝试加载:若未加载,父类加载器尝试按自身路径加载;若父类加载失败,子类加载器再尝试加载。
- 若所有加载器均无法加载,抛出
ClassNotFoundException
。
3.8 打破双亲委派机制
双亲委派的核心逻辑在ClassLoader.loadClass()
方法,打破方式包括:
- 自定义类加载器
- 继承
ClassLoader
,重写loadClass
方法(跳过父类委派逻辑)。 - 注意:JVM判断两个类是否相同的依据是“全限定名+类加载器”,同一类被不同加载器加载会视为不同类。
- 继承
- 线程上下文类加载器(SPI机制)
- 线程上下文类加载器默认是应用程序类加载器,允许父类加载器(如启动类加载器)通过它加载子类加载器路径下的类(如JDBC驱动)。
- 本质是“委托反转”,未完全打破双亲委派,而是补充其跨加载器访问能力。
- OSGi框架
- 支持双向委派(既可以委托父加载器,也可以委托子加载器),实现类的热部署和隔离,适用于模块化应用。
3.9 SPI机制
SPI(Service Provider Interface)是JDK的服务发现机制,允许接口与实现分离。
工作原理
- 在
ClassPath/META-INF/services
目录下,创建以接口全限定名命名的文件(如java.sql.Driver
)。 - 文件中写入接口实现类的全限定名(如
com.mysql.cj.jdbc.Driver
)。 - 框架通过
ServiceLoader.load(接口类)
扫描文件,加载并实例化实现类。
跨加载器访问问题
- SPI接口(如
java.sql.Driver
)由启动类加载器加载,而实现类(如MySQL驱动)在应用类路径,需通过线程上下文类加载器获取应用程序类加载器加载实现类:
ClassLoader cl = Thread.currentThread().getContextClassLoader();
3.10 JDBC与双亲委派
JDBC未真正打破双亲委派,而是通过线程上下文类加载器解决跨加载器访问:
DriverManager
(启动类加载器加载)通过SPI机制发现驱动实现类,再使用线程上下文类加载器(应用程序类加载器)加载驱动。- 驱动类加载时仍遵循双亲委派(先委托父加载器),核心规则未被打破。
四、运行时数据区
JVM在运行时管理的内存区域,分为线程私有和线程共享两类,各区域有明确的职责和生命周期。
4.1 线程私有区域(随线程创建/销毁)
4.1.1 程序计数器
- 作用:存储当前线程执行的字节码指令地址(如分支、循环、异常处理的跳转位置)。
- 特点:
- 线程私有,确保多线程切换后能恢复执行位置。
- 唯一不会发生
OutOfMemoryError
的区域(内存固定,仅存储地址)。
4.1.2 Java虚拟机栈
- 作用:存储方法调用的栈帧(
Stack Frame
),遵循“先进后出(FILO)”原则。 - 栈帧组成:
- 局部变量表:存放方法参数、局部变量、
this
对象(非静态方法),以“槽”为单位(long
/double
占2个槽,其他类型占1个)。 - 操作数栈:临时存储运算数据(如加法运算的操作数),深度在编译期确定。
- 帧数据:
- 动态链接:将方法的符号引用转换为直接引用(指向运行时常量池)。
- 方法出口:记录方法返回后的下一条指令地址(供程序计数器使用)。
- 异常表:记录异常捕获范围及处理指令位置。
- 局部变量表:存放方法参数、局部变量、
- 参数:通过
-Xss
设置栈大小(如-Xss256k
),默认值依赖操作系统。 - 异常:栈帧过多(如递归过深)会抛出
StackOverflowError
;栈扩展失败会抛出OutOfMemoryError
。
4.1.3 本地方法栈
- 作用:存储native方法的调用栈帧(与Java虚拟机栈类似)。
- 实现:HotSpot虚拟机将其与Java虚拟机栈合并,统一管理。
4.2 线程共享区域(随JVM启动/关闭)
4.2.1 堆
- 作用:存储所有对象实例和数组,是垃圾回收的主要区域。
- 特点:
- 内存最大的区域,可通过
-Xms
(初始大小)和-Xmx
(最大大小)设置(建议两者相等,减少动态调整开销)。 - 默认大小:
-Xms
为系统内存1/64,-Xmx
为系统内存1/4。
- 内存最大的区域,可通过
- 对象引用:栈的局部变量表中存储堆对象的引用(地址),而非对象本身。
- 异常:对象分配超过堆最大容量时,抛出
OutOfMemoryError
。
4.2.2 方法区
- 作用:存储类元信息、运行时常量池、静态变量、JIT编译后的代码等。
- 实现差异:
- JDK7及之前:以永久代(
PermGen Space
)实现,位于堆中,大小通过-XX:MaxPermSize
设置(易OOM)。 - JDK8及之后:以元空间(
MetaSpace
)实现,位于直接内存,大小通过-XX:MaxMetaspaceSize
设置(默认无上限,建议显式指定)。
- JDK7及之前:以永久代(
- 运行时常量池:常量池的运行时表示,存储编译期生成的常量和符号引用,加载后转换为直接引用(内存地址)。
- 字符串常量池:
- JDK6及之前:属于方法区(永久代),存储字符串副本。
- JDK7及之后:移至堆中,存储字符串对象的引用。
String.intern()
:将字符串加入常量池,JDK7+返回引用,JDK6返回副本。
4.3 直接内存
- 定义:不属于JVM规范,是操作系统直接管理的内存(堆外内存)。
- 引入背景:JDK1.4的NIO为解决堆内存GC影响性能、IO操作数据复制开销大的问题而引入。
- 分配方式:通过
ByteBuffer.allocateDirect(size)
分配。 - 参数:
-XX:MaxDirectMemorySize
设置最大大小(默认与堆-Xmx
相同)。 - 异常:分配超过最大限制时,抛出
OutOfMemoryError
。
4.4 静态变量的存放位置
- JDK7及之前:位于方法区(永久代)。
- JDK8及之后:随
Class
对象存放在堆中,脱离元空间。
4.5 JDK7与JDK8内存结构的区别
区域 | JDK7及之前 | JDK8及之后 |
---|---|---|
方法区实现 | 永久代(PermGen Space,堆内) | 元空间(MetaSpace,直接内存) |
静态变量位置 | 永久代 | 堆中的Class 对象 |
字符串常量池 | 永久代(属于运行时常量池) | 堆中(独立于运行时常量池) |
内存参数 | -XX:MaxPermSize 控制永久代大小 | -XX:MaxMetaspaceSize 控制元空间大小 |
4.6 可能发生内存溢出的区域
- Java虚拟机栈:栈帧过多(
StackOverflowError
)或栈扩展失败(OutOfMemoryError
)。 - 堆:对象分配超过最大堆容量(
OutOfMemoryError: Java heap space
)。 - 方法区/元空间:类元信息过多(如频繁动态生成类,
OutOfMemoryError: Metaspace
)。 - 直接内存:分配超过最大限制(
OutOfMemoryError
,错误信息含Direct buffer memory
)。
五、垃圾回收机制
垃圾回收(GC)负责回收线程共享区域(堆和方法区)中不再使用的对象,避免内存泄漏。
5.1 回收范围
- 堆:主要回收不再使用的对象实例。
- 方法区:回收不再使用的类(需满足类卸载的三个条件,见“类的生命周期-卸载”)。
5.2 对象存活判定算法
5.2.1 引用计数法
- 原理:为每个对象维护引用计数器,被引用时+1,引用失效时-1,计数器为0则标记为垃圾。
- 优点:实现简单,判定高效。
- 缺点:无法解决循环引用问题(如
A.ref = B
且B.ref = A
,计数器始终不为0),Java未采用。
5.2.2 可达性分析算法(Java采用)
- 原理:以“GC Root”为起点,遍历对象引用链,不可达的对象标记为垃圾。
- GC Root包括:
- 线程栈帧中的局部变量、方法参数。
- 类的静态变量(由系统类加载器加载的
Class
对象引用)。 - 同步锁(
synchronized
)持有的对象。 - 本地方法栈中引用的全局对象。
5.3 五种对象引用类型
5.3.1 强引用
- 最常见的引用(如
Object obj = new Object()
),只要引用链可达,对象就不会被回收。 - 即使内存不足,也不会回收强引用对象,会直接抛出
OutOfMemoryError
。
5.3.2 软引用(SoftReference
)
- 仅被软引用关联的对象,在内存不足时会被回收(用于缓存场景)。
- 需配合
ReferenceQueue
:对象被回收后,软引用会进入队列,可通过遍历队列清除软引用本身。
ReferenceQueue<Object> queue = new ReferenceQueue<>();
SoftReference<Object> softRef = new SoftReference<>(new Object(), queue);
5.3.3 弱引用(WeakReference
)
- 无论内存是否充足,只要发生GC就会回收仅被弱引用关联的对象(如
WeakHashMap
的key)。 - 同样需通过
ReferenceQueue
回收弱引用本身。
5.3.4 虚引用(PhantomReference
)
- 无法通过虚引用获取对象,唯一作用是在对象被回收时收到通知(用于跟踪直接内存回收)。
- 必须配合
ReferenceQueue
使用。
5.3.5 终结器引用(FinalReference
)
- 由JVM自动创建,关联待回收的对象,用于在对象回收前执行
finalize()
方法。 - 对象被标记为垃圾后,会先进入
Finalizer
队列,由FinalizerThread
执行finalize()
,之后才真正回收。
5.4 垃圾回收算法
5.4.1 评价标准
- 吞吐量:CPU用于执行用户代码的时间占比(吞吐量=用户时间/(用户时间+GC时间))。
- 堆使用效率:垃圾回收后有效内存的利用率。
- 最大暂停时间(STW):GC导致用户线程暂停的最长时间(影响用户体验)。
5.4.2 标记-清除算法
- 流程:
- 标记:遍历所有对象,标记存活对象。
- 清除:回收未标记的垃圾对象,释放内存。
- 优点:实现简单,无需移动对象。
- 缺点:
- 内存碎片化严重,可能导致大对象无法分配连续内存。
- 分配效率低,需遍历空闲链表寻找合适内存块。
5.4.3 复制算法
- 流程:
- 将堆分为大小相等的两块(From区和To区),仅使用From区分配对象。
- GC时,将From区的存活对象复制到To区,清空From区。
- 交换From和To区的角色,重复使用。
- 优点:无内存碎片,分配效率高(直接指针碰撞)。
- 缺点:堆利用率低(仅50%),复制大对象成本高。
5.4.4 标记-整理算法
- 流程:
- 标记:同标记-清除算法,标记存活对象。
- 整理:将存活对象向堆的一端移动,然后清除边界外的垃圾。
- 优点:无内存碎片,堆利用率100%。
- 缺点:整理阶段需移动对象,效率较低(需优化移动算法)。
5.5 分代GC算法
结合上述算法的优势,根据对象存活周期划分区域,针对性回收。
5.5.1 分代假说(设计基础)
- 短命对象假说:绝大多数对象存活时间短(如临时变量)。
- 弱分代假说:老对象极少引用新对象,跨代引用占比低。
5.5.2 内存分代划分
分代 | 存储对象类型 | 特点 |
---|---|---|
年轻代 | 新创建对象、短生命周期对象 | 空间小(堆的1/3左右)、GC频繁、回收快 |
老年代 | 长生命周期对象 | 空间大(堆的2/3左右)、GC频率低、回收慢 |
元空间 | 类元信息、常量等 | 几乎不回收,仅类卸载时触发 |
- 年轻代细分:
- Eden区:对象初始分配区域(占年轻代80%)。
- Survivor区(S0/S1):各占10%,用于存放Eden区GC后存活的对象,始终有一个为空。
5.5.3 回收策略
- Minor GC(年轻代回收)
- 触发:Eden区满时。
- 算法:复制算法(将Eden+S0的存活对象复制到S1,清空Eden和S0,下次GC交换S0/S1)。
- 特点:存活对象少,STW时间短(毫秒级)。
- Major GC(老年代回收)
- 触发:老年代空间不足(如年轻代对象存活次数达阈值后进入老年代)。
- 算法:标记-清除或标记-整理算法(避免复制大对象)。
- 特点:存活对象多,STW时间长(秒级)。
- Full GC(全量回收)
- 触发:老年代+年轻代均不足、元空间满等极端情况。
- 行为:同时回收年轻代、老年代和元空间。
- 影响:STW时间最长,应尽量避免(如通过调整参数减少Full GC频率)。
5.5.4 跨代引用优化(卡表)
- 问题:Minor GC时若扫描整个老年代检查跨代引用,会降低效率。
- 解决方案:将老年代划分为“卡页”(默认512字节/页),若老年代对象引用年轻代对象,标记对应卡页为“脏页”。
- 优化效果:Minor GC仅需扫描“脏页”,大幅减少扫描范围。
5.6 堆内存调整参数
参数 | 作用 | 示例 |
---|---|---|
-Xms | 堆初始大小(建议与-Xmx 相等) | -Xms512m |
-Xmx | 堆最大大小 | -Xmx2g |
-Xmn | 年轻代大小(老年代=堆大小-年轻代) | -Xmn1g |
-XX:SurvivorRatio | Eden与S0/S1的比例(默认8:1:1) | -XX:SurvivorRatio=8 |
-XX:+PrintGCDetails | 打印GC详细日志 | - |
-verbose:gc | 打印简单GC日志 | - |
5.7 垃圾回收器
不同回收器针对不同场景优化,需根据应用特点选择。
5.7.1 Serial+Serial Old(JDK9废弃)
- 年轻代(Serial):单线程,复制算法,适用于单CPU环境,STW时间随堆增大变长。
- 老年代(Serial Old):单线程,标记-整理算法,同上。
- 参数:
-XX:+UseSerialGC
。
5.7.2 ParNew+CMS(JDK14废弃)
- 年轻代(ParNew):多线程版Serial,复制算法,多CPU下STW时间短。
- 老年代(CMS):关注STW时间,标记-清除算法,分四阶段:
- 初始标记(STW,标记GC Root直接关联对象)。
- 并发标记(与用户线程并行,标记所有存活对象)。
- 重新标记(STW,修正并发标记的遗漏)。
- 并发清理(与用户线程并行,回收垃圾)。
- 优点:STW时间短,用户体验好。
- 缺点:内存碎片、浮动垃圾(并发清理时产生的新垃圾)、可能退化为Serial Old。
- 参数:
-XX:+UseConcMarkSweepGC
(指定老年代用CMS,年轻代默认ParNew)。
5.7.3 Parallel Scavenge+Parallel Old(JDK14废弃)
- 年轻代(Parallel Scavenge):多线程,复制算法,注重吞吐量,支持自适应调整堆参数(如动态调整年轻代大小)。
- 老年代(Parallel Old):多线程,标记-整理算法,配合Parallel Scavenge提升吞吐量。
- 参数:
-XX:+UseParallelGC
(JDK8默认,年轻代Parallel Scavenge+老年代Parallel Old)。 - 调优参数:
-XX:GCTimeRatio=n
:设置吞吐量目标(n=99表示GC时间不超过1%)。-XX:MaxGCPauseMillis=n
:设置最大STW时间(毫秒)。
5.7.4 G1(JDK9默认)
G1(Garbage-First)是面向大堆(多GB)的回收器,兼顾吞吐量和STW时间。
- 特点:
- 堆划分为大小相等的Region(1-32MB,2的幂次),每个Region动态承担Eden、Survivor或Old角色。
- 优先回收垃圾占比高的Region(“Garbage-First”),最大化回收收益。
- 年轻代用复制算法,老年代通过混合回收(Mixed GC)逐步清理。
- 支持通过
-XX:MaxGCPauseMillis
(默认200ms)设置目标STW时间,动态调整回收范围。 - 大对象(>Region的50%)直接分配到H-Region,避免频繁晋升。
- 无内存碎片(复制算法自然压缩)。
- 回收流程:
- 初始标记(STW,标记GC Root直接关联对象)。
- 并发标记(与用户线程并行,遍历存活对象)。
- 最终标记(STW,修正并发标记的遗漏)。
- 筛选回收(STW,按目标时间选择Region回收,复制存活对象)。
- 参数:
-XX:+UseG1GC
:指定使用G1。-XX:MaxGCPauseMillis=n
:设置目标STW时间。-XX:G1HeapRegionSize=n
:设置Region大小(1-32MB,2的幂次)。
六、JVM监控与诊断工具
- Arthas:阿里开源工具,支持GC监控、线程分析、类热部署等,适合在线问题排查。
- MAT(Eclipse Memory Analyzer Tool):离线分析堆快照,定位内存泄漏(如大对象、内存溢出原因)。
- JDK自带工具:
jps
:查看Java进程ID。jstat
:监控GC统计信息(如jstat -gc 进程ID 间隔时间
)。jmap
:生成堆快照(jmap -dump:format=b,file=heap.hprof 进程ID
)。jstack
:查看线程栈信息(定位死锁、阻塞等问题)。hsdb
:JDK自带调试工具,查看内存中的对象信息。