basic - JVM
JVM概述
Java 源码 → 字节码 → JVM解释器一条条解释执行
JIT
即时编译: Just-In-Time Compiler,
解释执行太慢, JIT 对于热点代码 (计数器判断) , 会即时编译成机器码

优化的方式
- 方法内联: 直接把方法塞进调用出
代码块收起展开
int add(int a, int b) { return a+b; }
x=add(1, 2);// 会优化为 x = 1+2;-
逃逸分析 会判断一个对象会不会逃出
当前方法和线程如果不会逃逸, 可能做如下优化- 栈上匹配
- 标量替换
-
循环优化
- 循环展开
- 消除边界检查的一部分开销
- 可能再长循环中途切换成编译好的机器码跑
三大功能
| 解释与运行 .class->jvm—>机器码 | 内存管理 GC | 即时编译 JIT |
|---|---|---|
| 对字节码文件中的指令, 实时的解释成机器码, 让计算机执行 | - 自动为对象、方法等分配内存 - 自动的垃圾回收机制,回收不再使用的对象 | 对热点代码进行优化,提升执行效率 |
常见虚拟机
| 名称 | 作者 | 支持版本 | 社区活跃度(GitHub Star) | 特性 | 适用场景 |
|---|---|---|---|---|---|
| HotSpot(Oracle JDK版) | Oracle | 所有版本 | 高(闭源) | 使用最广泛,稳定可靠,社区活跃,支持JIT,Oracle JDK默认虚拟机 | 默认 |
| HotSpot(OpenJDK版) | Oracle | 所有版本 | 中(16.1k) | 同上,开源,OpenJDK默认虚拟机 | 对JDK有二次开发需求 |
| GraalVM | Oracle | 11、17、19(企业版支持8) | 高(18.7k) | 多语言支持(Ruby、Python、C++等),高性能,支持JIT和AOT | 微服务、云原生架构、多语言混合编程 |
| Dragonwell JDK(龙井) | Alibaba | 标准版8、11、17;扩展版11、17 | 低(3.9k) | 基于OpenJDK增强,高性能、bug修复、安全性提升,支持JWarmup、ElasticHeap、Wisp | 电商、物流、金融等高性能场景 |
| Eclipse OpenJ9(原IBM J9) | IBM | 8、11、17、19、20 | 低(3.1k) | 高性能、可扩展,支持JIT和AOT | 微服务、云原生架构 |
堆内存回收
回收的判定
引用计数法
查看垃圾回收日志
- 使用
-verbose:gc参数
可达性分析
- 将对象分为两类: 根对象(GC Root) 和 普通对象
GC Root 回收对象
强引用不可回收
-
线程
Thread对象, 引用线程栈帧中的方法参数、局部变量等 -
系统类加载器加载的
java.lang.Class对象 比如AppClassLoader -
监视器对象, 用来保存同步锁
synchronized关键字持有的对象 -
本地方法 (native) 调用时使用的全局对象
软引用
- 比较弱的引用关系, 如果一个对象只有软引用关联到它, 当程序内存不足时, 就会将软引用中的数据进行回收
- 提供SoftReference来实现软引用, 常用于缓存中
- 当创建软引用的时候,会创建一个引用队列, 当内存不足的时候,会将软引用对应的对象数据删掉,然后再把软引用放到队列里面。
弱引用
- WeakReference类实现弱引用, 主要在ThreadLocal使用
- 也能用引用队列进行回收
虚引用&终结器
-
虚引用
PhantomReference:当对象被垃圾回收器回收时可以接收到对应的通知, 直接内存通过虚引用可以及时知道直接内存对象不再使用从而回收内存 -
终结器引用: 终结器引用会关联对象并放置在
Finalizer类中的引用队列中, 接着由FinalizerThread线程从队列中获取对象, 然后执行对象的finalize方法, 在对象第二次被回收时, 该对象才是真正地被回收
| 引用类型 | 被 GC 回收时机 | 是否可通过 get() 获取对象 | 典型用途 | 一句话理解 |
|---|---|---|---|---|
| 强引用(StrongReference) | 永不自动回收 | ✅ | 普通引用,日常使用 | 我一直活着,除非你手动断开 |
| 软引用(SoftReference) | 内存不足时回收 | ✅ | 缓存 | 我能活尽量活 |
| 弱引用(WeakReference) | 下次 GC 即回收 | ✅ | ThreadLocal、WeakHashMap | 下次 GC 我就走 |
| 虚引用(PhantomReference) | 对象 finalize 后、即将回收前 | ❌ | 跟踪对象销毁、直接内存回收 | 我走了记得收 |
| 终结器引用(FinalReference) | 对象重写 finalize() 时使用 | ⚙️(内部使用) | Finalizer 机制 | 我死前乱叫一声 |
如何用java代码主动加载一个类?
- 法1: 使用
Class.forName(), 使用当前类的类加载器去加载指定的类 - 法2: 获取到类加载器, 通过类加载器的
loadClass方法指定某个类加载器加载
代码块收起展开
//获取`main`方法所在类的类加载器, appClassLoader
ClassLoader classLoader = Demo1.class.getClassLoader();
System.out.println(classLoader);
//使用appClassLoader加载 com.itheima.my.A
Class<?> clazz = classLoader.loadClass("com.itheima.my.A");
字节码文件
主要内容
魔数
- 文件拓展名如
.txt、.java可以任意修改, 不决定文件类型 - 软件使用文件的头几个字节(文件头)来校验文件的类型, 如果软件不支持就会出错
| 文件类型 | 字节数 | 文件头 |
|---|---|---|
| JPEG(jpg) | 3 | FFD8FF |
| PNG(png) | 4 | 89504E47(文件尾也有要求) |
| BMP(bmp) | 2 | 424D |
| XML(xml) | 5 | 3C3F786D6C |
| AVI(avi) | 4 | 41564920 |
| Java字节码文件(.class) | 4 | CAFEBABE |
主副版本号
字节码文件的组成部分-主副版本号
- 主版本号用来标识大版本号, JDK1.0-1.1使用了45.0-45.3, JDK1.2是46之后每升级一个大版本就加1;
- 副版本号是当主版本号相同时—>作为区分不同版本的标识, 一般只需要关心主版本号
- 版本号的作用: 判断当前字节码的版本和运行时的JDK是否兼容
1.2 之后的大版本号计算方法: 主版本号 - 44
ps:
- 高版本可以加载低版本, 但是API用法不同
- jdk8 —> jdk17, 非常难, 最好升级jdk / 降低依赖的版本
常量池
- String常量不直接存放字面量值, 字段名和字段值都是通过索引指向字符串字面量, 节省开支
- 在Java源码层面,你写的”hello”、
3.14、 final int A = 10;都是常量字面量
这些在编译时会被记录进
·class文件中的一个结构里,名字叫常量池表 而是一张索引表,里面可能包含:- 字面量(Literal):字符串、数字、布尔、类名、方法名等等;
- 符号引用(Symbolic Reference):指向类、字段、方法、接口的符号化描述。
运行时常量池
当类加载器把.class文件读进JVM时, 虚拟机会从字节码提取这张”常量字典”, 然后放入内存的一个区域, 称为运行时常量池
方法
-
操作数栈是临时存放数据的地方
-
局部变量表是存放方法中的局部变量的位置
-
i++ 和 k+=2 效率一样高, 都是直接
INC操作LVT
常用工具
javap
- JDK自带的反编译工具, 适合在服务器上查看字节码文件内容
- 直接输入
javap查看所有参数 - 输入
javap -v 字节码文件名称查看具体的字节码信息 如果jar包需要先使用jar -xvf命令解压
jclasslib
arthas
方法区
存储类的元信息
方法区是用来存储每个类的基本信息, 一般称之为InstanceKlass对象 在类的加载阶段完成: 加载, 准备, 连接, 解析, 使用, 卸载