basic - JVM

JVM概述

Java 源码 → 字节码 → JVM解释器一条条解释执行

Pasted image 20260421234944

JIT

即时编译: Just-In-Time Compiler, 解释执行太慢, JIT 对于热点代码 (计数器判断) , 会即时编译成机器码 Pasted image 20260421232211

优化的方式

  1. 方法内联: 直接把方法塞进调用出
代码块JAVA · 2 行收起展开
int add(int a, int b)  { return a+b; }
x=add(1, 2);// 会优化为 x = 1+2;
  1. 逃逸分析 会判断一个对象会不会逃出当前方法线程 如果不会逃逸, 可能做如下优化

    • 栈上匹配
    • 标量替换
  2. 循环优化

    • 循环展开
    • 消除边界检查的一部分开销
    • 可能再长循环中途切换成编译好的机器码跑

三大功能

解释与运行
.class->jvm—>机器码
内存管理 GC即时编译 JIT
对字节码文件中的指令,
实时的解释成机器码,
让计算机执行
- 自动为对象、方法等分配内存
- 自动的垃圾回收机制,回收不再使用的对象
对热点代码进行优化,提升执行效率

常见虚拟机

名称作者支持版本社区活跃度(GitHub Star)特性适用场景
HotSpot(Oracle JDK版)Oracle所有版本高(闭源)使用最广泛,稳定可靠,社区活跃,支持JIT,Oracle JDK默认虚拟机默认
HotSpot(OpenJDK版)Oracle所有版本中(16.1k)同上,开源,OpenJDK默认虚拟机对JDK有二次开发需求
GraalVMOracle11、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)IBM8、11、17、19、20低(3.1k)高性能、可扩展,支持JIT和AOT微服务、云原生架构

堆内存回收

回收的判定

引用计数法

查看垃圾回收日志

  • 使用-verbose:gc参数 Pasted image 20260421235522

可达性分析

  • 将对象分为两类: 根对象(GC Root) 和 普通对象 Pasted image 20260421235851

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. 法1: 使用 Class.forName() , 使用当前类的类加载器去加载指定的类
  2. 法2: 获取到类加载器, 通过类加载器的loadClass方法指定某个类加载器加载
代码块JAVA · 6 行收起展开
//获取`main`方法所在类的类加载器, appClassLoader
ClassLoader classLoader = Demo1.class.getClassLoader();
System.out.println(classLoader);

//使用appClassLoader加载 com.itheima.my.A
Class<?> clazz = classLoader.loadClass("com.itheima.my.A");

字节码文件

主要内容

Pasted image 20260422130158

魔数

  • 文件拓展名如.txt.java可以任意修改, 不决定文件类型
  • 软件使用文件的头几个字节(文件头)来校验文件的类型, 如果软件不支持就会出错
文件类型字节数文件头
JPEG(jpg)3FFD8FF
PNG(png)489504E47(文件尾也有要求)
BMP(bmp)2424D
XML(xml)53C3F786D6C
AVI(avi)441564920
Java字节码文件(.class)4CAFEBABE

主副版本号

字节码文件的组成部分-主副版本号

  • 主版本号用来标识大版本号, 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时, 虚拟机会从字节码提取这张”常量字典”, 然后放入内存的一个区域, 称为运行时常量池

方法

  • 操作数栈是临时存放数据的地方

  • 局部变量表是存放方法中的局部变量的位置

    Pasted image 20260422135518
  • i++ 和 k+=2 效率一样高, 都是直接 INC 操作 LVT

常用工具

javap
  • JDK自带的反编译工具, 适合在服务器上查看字节码文件内容
  • 直接输入javap查看所有参数
  • 输入 javap -v 字节码文件名称 查看具体的字节码信息 如果jar包需要先使用jar -xvf命令解压
jclasslib
arthas

方法区

Pasted image 20260428235151

存储类的元信息

方法区是用来存储每个类的基本信息, 一般称之为InstanceKlass对象 在类的加载阶段完成: 加载, 准备, 连接, 解析, 使用, 卸载