JVM 知识点
知识点
- class 文件结构
- classloader
- JVM 运行时数据区
- 垃圾回收器和垃圾回收算法
- JIT
类加载机制
JVM把.class
文件加载到内存中时,创建对应的class
对象,这个过程称之为类的加载机制。
类的加载过程
Loading -> Linking -> Initializing
加载、连接(验证、准备、解析)、初始化。
- 加载:查找文件。通过类的全限定名
- 初始化:为类的静态变量赋值,然后执行类的初始化语句(static代码块)。
初始化过程:
- 类的初始化是在类的加载和链接完成之后开始,这是固定顺序;
- 如果存在父类,且父类没有初始化,则先初始化直接父类;
- 如果类中存在初始化语句,顺序执行初始化语句。
类的初始化的时机:包括主动引用和被动引用。
- 创建类的实例(四种方式:new、反射、反序列化、克隆)
- 访问类中的某个静态变量,或者对静态变量进行赋值
- 调用类的静态方法
- 反射
Class.forName
- 子类的初始化,会先完成父类的初始化(接口除外)
- 主动执行
main
方法
类初始化过程总结
-
对类的主动引用:new一个对象、调用类变量、类方法;反射;父类还未初始化时;执行主类(main)。
-
被动引用:
(1)子类引用父类静态字段,不会导致子类初始化。
class ClassLoadingTest {
public static void main(String[] args) {
System.out.println(SubClass.value);
}
}
class SuperClass {
static {
System.out.println("Superclass init");
}
public static int value = 123;
}
class SubClass extends SuperClass {
static {
System.out.println("Subclass init");
}
}
结果是:
Superclass init
123
可以看到子类并未初始化。
(2)通过数组定义来引用类,不会导致类的初始化。
class ClassLoadingTest {
public static void main(String[] args) {
SuperClass[] superClasses = new SuperClass[10];
}
}
这里,main
方法的执行结果是什么都不打印。这种情况是类虽然被加载了,但未初始化。
(3)常量在编译阶段存入调用类的常量池中,本质上没有直接引用该类,不会出发类的初始化。
即value
字段用static final
修饰,SuperClass
不会初始化,打印结果为:
123
类加载器
类加载器是类加载流程的实现者。
JDK自带的Class loader:
BootStrap ClassLoader
:自带的引导类加载器。由C/C++语言实现。在Java中打印为null,加载Java的核心类库Extension ClassLoader
:Launcher
的内部类Application ClassLoader
:Launcher
的内部类
问题:为什么需要自定义classLoader?
- 适配环境隔离(Tomcat中就有自定义类加载器)
- 从不同的数据源加载类
- 防止源码泄露
双亲委派模型
类的加载时,会向上询问是否加载,同时从上向下尝试是否可加载该类。
双亲委派模型的作用:
- 避免类的重复加载;
- 保护程序安全,防止Java核心语言环境被破坏。
JVM 运行时数据区
运行时数据区是规范的叫法,一般习惯叫做 JVM 内存结构。JVM 的内存结构可以分为公有和私有两部分。公有指的是所有线程都共享的部分,指的是 Java 堆、方法区、常量池。私有指的是每个线程的私有数据,包括:PC寄存器、Java 虚拟机栈、本地方法栈。
公有部分:Java堆、方法区、常量池
1、Java 堆
大部分实例对象在堆中分配内存。小部分小对象直接在栈上分配。
JVM 通过逃逸分析,分析出新对象的使用范围,就可能将对象在栈上进行分配。栈分配可以快速地在栈帧上创建和销毁对象,不用再将对象分配到堆空间,可以有效地减少 JVM 垃圾回收的压力。
Java 堆根据对象存活时间的不同,被分为年轻代、老年代两个区域,年轻代还被进一步划分为 Eden 区、From Survivor 0、To Survivor 1 区。如下图所示。
当有对象需要分配时,一个对象永远优先被分配在年轻代的 Eden 区,等到 Eden 区域内存不够时,Java 虚拟机会启动垃圾回收。此时 Eden 区中没有被引用的对象的内存就会被回收,而一些存活时间较长的对象则会进入到老年代。在 JVM 中有一个名为 -XX:MaxTenuringThreshold 的参数专门用来设置晋升到老年代所需要经历的 GC 次数,即在年轻代的对象经过了指定次数的 GC 后,将在下次 GC 时进入老年代。
2、方法区
存储Java 字节码文件的数据的一个区域。也就是说,它存储了一个类的结构信息,如常量池、字段和方法数据等。可以看到,常量池其实是存放在方法区中的,但《Java 虚拟机规范》将常量池和方法区放在同一个等级上,这点我们知晓即可。方法区在不同版本的虚拟机有不同的表现形式,例如在 1.7 版本的 HotSpot 虚拟机中,方法区被称为永久代(Permanent Space),而在 JDK 1.8 中则被称之为 MetaSpace。
私有部分:PC寄存器、Java 虚拟机栈、本地方法栈
1、PC 寄存器
Program Counter Register,指的是保存线程当前正在执行的方法。如果这个方法不是 native 方法,那么 PC 寄存器就保存 Java 虚拟机正在执行的字节码指令地址。如果是 native 方法,那么 PC 寄存器保存的值是 undefined。任意时刻,一条 Java 虚拟机线程只会执行一个方法的代码,而这个被线程执行的方法称为该线程的当前方法,其地址被存在 PC 寄存器中。
2、Java 虚拟机栈
Java 虚拟机栈,这个栈与线程同时创建,用来存储栈帧,即存储局部变量与一些过程结果的地方。栈帧存储的数据包括:局部变量表、操作数栈。
3、本地方法栈
当 Java 虚拟机使用其他语言(例如 C 语言)来实现指令集解释器时,也会使用到本地方法栈。
垃圾回收的几种类型
垃圾回收的几种术语:Minor GC、Major GC、Young GC、Old GC、Full GC、Stop-The-World。
- Minor GC:从年轻代空间回收内存被称为 Minor GC,有时候也称之为 Young GC。
- Major GC:从老年代空间回收内存被称为 Major GC,有时候也称之为 Old GC。
- Full GC:Full GC 是清理整个堆空间 —— 包括年轻代、老年代和永久代(如果有的话)。因此 Full GC 可以说是 Minor GC 和 Major GC 的结合。
- Stop-The-World:是指在进行垃圾回收时因为标记或清理的需要,必须让所有执行任务的线程停止执行任务,从而让垃圾回收线程回收垃圾的时间间隔。
JVM 参数
部分参数:
参数 | 含义 |
---|---|
-Xms | 初始堆大小 |
-Xmx | 最大堆空间 |
-Xmn | 设置新生代大小 |
-XX:SurvivorRatio | 设置新生代eden空间和from/to空间的比例关系 |
-XX:PermSize | 方法区初始大小 |
-XX:MaxPermSize | 方法区最大大小 |
-XX:MetaspaceSize | 元空间GC阈值(JDK1.8) |
-XX:MaxMetaspaceSize | 最大元空间大小(JDK1.8) |
-Xss | 栈大小 |
-XX:MaxDirectMemorySize | 直接内存大小,默认为最大堆空间 |
JVM 调优原则
JVM 调优不是常规手段,性能问题一般第一选择是优化程序,最后的选择才是进行 JVM 调优。
JVM 的自动内存管理本来就是为了将开发人员从内存管理的泥潭里拉出来。即使不得不进行 JVM 调优,也绝对不能拍脑门就去调整参数,一定要全面监控,详细分析性能数据。
JVM 调优时机
不得不考虑进行JVM调优的是那些情况呢?
- Heap内存(老年代)持续上涨达到设置的最大内存值;
- Full GC 次数频繁;
- GC 停顿时间过长(超过1秒);
- 应用出现OutOfMemory 等内存异常;
- 应用中有使用本地缓存且占用大量内存空间;
- 系统吞吐量与响应性能不高或下降。
JVM 调优步骤
一般情况下,JVM调优可通过以下步骤进行:
- 分析系统系统运行情况:分析GC日志及dump文件,判断是否需要优化,确定瓶颈问题点;
- 确定JVM调优量化目标;
- 确定JVM调优参数(根据历史JVM参数来调整);
- 依次确定调优内存、延迟、吞吐量等指标;
- 对比观察调优前后的差异;
- 不断的分析和调整,直到找到合适的JVM参数配置;
- 找到最合适的参数,将这些参数应用到所有服务器,并进行后续跟踪。
参考资料
Java 虚拟机(JVM)入门教程
【JVM进阶之路】十:JVM调优总结 - 博客园
一般的Java项目需要JVM调优吗?
【性能优化】面试官:Java中的对象都是在堆上分配的吗?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!