1. JVM介绍
Java虚拟机(Java Virtual Machine 简称JVM)是运行所有Java程序的抽象计算机,是Java编程语言的运行环境,它是Java最具吸引力的特性之一。
JVM本质上是一个运行在计算机上的程序,他的职责是运行Java字节码文件,这就是Java跨平台的本质原因。由于Java是开放的,有越来越多的编程语言开发的程序也可以运行在JVM之上,例如 大数据开发的Scala、安卓开发的Kotlin以及脚本语言Grovvy等。
JVM的三大核心功能:
- 解释执行虚拟指令:对字节码文件中的指令,实时的解释成机器码,让计算机执行;
- 内存管理:Java虚拟机会自动为对象、方法等分配内存,并且内部提供了垃圾回收机制,会自动将内存中没有使用的对象回收掉,不像C/C++需要手动释放内存;
- 即时编译(JIT):对热点代码进行优化(提前预热),提升执行效率,最终能达到接近C/C++语言的运行性能,甚至在特定场景下实现超越;
市面上常见的JVM:
名称 | 作者 | 支持版本 | 社区活跃度(star数) | 特性 | 适用场景 |
---|---|---|---|---|---|
HotSpot(Oracle JDK版) | Oracle | 所有版本 | 高(闭源) | 使用最广泛,稳定可靠,社区活跃,JIT支持,OracleJDK默认虚拟机 | 默认 |
HotSpot(open JDK版) | Oracle | 所有版本 | 中(开源) | 同上,OpenJDK默认虚拟机 | 默认,对DK有二次开发需求 |
GraalVM | Oracle | 11,17,19,企业支持 | 高(开源) | 多语言支持,高性能、JIT、AOT支持 | 微服务、云原生架构,需要多语言混合编程 |
DragonwellJDK龙井 | Alibaba | 标准版8,11,17 扩展版11,17 |
低(开源) | 对OpenJDK进行增强,而且高性能、bug修复、安全性提升,提供了JWarmup、ElasticHeap、Wisp特性支持 | 电商、物流、金融领域,对性能要求比较高 |
Eclipse OpenJ9(原BMJ9) | IBM | 8,11,17,19,20 | 低(开源) | 高性能、可扩展、JIT、AOT特性支持 | 微服务、云原生架 |
Java虚拟机规范:由Oracle制定,它定义了Java虚拟机在设计和实现使需要遵守的一系列规范。主要包含clss字节码文件格式、类和接口的加载和初始化、指令集、异常处理、内存模型等内容。(不仅支持)
文档地址:https://docs.oracle.com/javase/specs/index.html
HotSpot虚拟机发展史:
查看jdk自带的虚拟机:java -version
2. 字节码文件详解
2.1 Java虚拟机的组成
JVM的组成部分:
-
类加载器(Class Loader)
-
运行时数据区域(Runtime Data Areas)
-
执行引擎(Execution Engine)
-
本地接口(Java Native Interface,JNI)
2.2 字节码文件的组成
Java字节码以二进制的形式存储在.class文件中,无法直接用记事本查看,需要借助反编译工具。
jclasslib下载地址:https://github.com/ingokegel/jclasslib/releases/
字节码文件主要由5部分组成:
-
基本信息:魔数、字节码文件对应的Java版本号、访问标识(public final等等),父类和接口。
-
常量池:保存了字符串常量、类或接口名、字段名主要在字节码指令中使用。
-
字段:当前类或接口声明的字段信息。
-
方法:当前类或接口声明的方法信息,所对应的字节码指令。
-
属性:类的属性,比如源码的文件、内部类的列表等。
2.2.1 常量池
-
字节码文件中常量池的作用:避免相同的内容重复定义,节省空间。
-
常量池中的每个数据都有编号,编号从1开始。在字段或者字节码指令中通过编号可以快速的找到对应的数据。
-
字节码指令中通过编号引用到常量池的过程称之为符号引用。
示例:在类字段上定义两个内容相同的常量
package cn.z3inc.demo;
/**
* 测试字节码文件常量池
*
* @author 白豆五
* @date 2023/10/15
* @since JDK8
*/
public class ConstantPoolTest {
public static final String STR1 = "人因梦想而伟大";
public static final String STR2 = "人因梦想而伟大";
public static void main(String[] args) {
ConstantPoolTest constantPoolTest = new ConstantPoolTest();
}
}
使用jclasslib工具查看该类的字节码文件:
2.2.2 方法
(1) 字节码中的方法区域是存放字节码指令的核心位置,字节码指令的内容存放在方法的Code属性中。
(2) 操作数栈是临时存放数据的地方,局部变量表是存放方法中的局部变量的位置。
累加的执行流程:
字节码指令大概执行流程:
- iconst_0:将常量0放入操作数栈
- istore_1:把常量0从操作数栈中弹出,放入局部变量表1号位置。(此时操作数栈中不会有常量0)
- iload_1:将局部变量表1号位置的数据加载到操作数栈中。(把局部变量表中1号位置的数据复制一份放入操作数栈中)
- iconst_1:将常量1放入操作数栈(此时操作数栈中的数据为:0,1)
- iadd:将操作数栈顶部的两个数据进行累加,结果放入栈中(此时操作数栈中的数据为:1)
- istore_2:把累加结果从操作数栈中弹出,放入局部变量表2号位置。
- return:方法结束,返回
i++的执行流程:
字节码指令大概执行流程:
- iconst_0:将常量0放入操作数栈
- istore_1:把常量0从操作数栈中弹出,放入局部变量表1号位置。
- iload_1:将局部变量表1号位置的数据加载到操作数栈中。
- iinc 1 by 1:将局部变量表中1号位置的数据增加1。(此时i为1)
- istore_1:把常量0从操作数栈中弹出,放入局部变量表1号位置。(数据会被覆盖,最终i为0)
- return:方法结束,返回
++i的执行流程:(字节码指令条数越多,操作性能会比较低些)
//源码
public static void main(String[] args) {
int i = 0;
i = i++;
}
iconst_0
istore_1
iinc 1 by 1 //在++i中inc在load指令之前执行
iload_1
istore_1
return
最终i的结果为:1。
2.2.3 常用的字节码工具
① javap -v命令:
javap是JDK自带的反编译工具,可以通过控制台查看字节码文件的内容。适合在服务器上查看字节码文件
内容。
命令行输入javap,查看javap命令的所有参数:
-help --help -? 输出此用法消息
-version 版本信息
-v -verbose 输出附加信息
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类
和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的
系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示最终常量
-classpath <path> 指定查找用户类文件的位置
-cp <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置
查看字节码文件的内容信息:java -v 字节码文件名称
(如果class文件在层级比较深的路径下,需要加上全路径名)
E:\Code\Other\jiuye\java-demo\jvm-demo\jvm01-base\target\classes\cn\z3inc\demo 的目录
2023/10/15 19:29 <DIR> .
2023/10/15 18:24 <DIR> ..
2023/10/15 18:24 546 ConstantPoolTest.class
2023/10/15 19:29 419 Demo2.class
2 个文件 965 字节
2 个目录 148,872,474,624 可用字节
E:\Code\Other\jiuye\java-demo\jvm-demo\jvm01-base\target\classes\cn\z3inc\demo>javap -v Demo2.class
Classfile /E:/Code/Other/jiuye/java-demo/jvm-demo/jvm01-base/target/classes/cn/z3inc/demo/Demo2.class
Last modified 2023-10-15; size 419 bytes
MD5 checksum 2d530172474d18c31f88a970efaa624a
Compiled from "Demo2.java"
public class cn.z3inc.demo.Demo2
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#19 // java/lang/Object."<init>":()V
#2 = Class #20 // cn/z3inc/demo/Demo2
#3 = Class #21 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Lcn/z3inc/demo/Demo2;
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 args
#14 = Utf8 [Ljava/lang/String;
#15 = Utf8 i
#16 = Utf8 I
#17 = Utf8 SourceFile
#18 = Utf8 Demo2.java
#19 = NameAndType #4:#5 // "<init>":()V
#20 = Utf8 cn/z3inc/demo/Demo2
#21 = Utf8 java/lang/Object
{
public cn.z3inc.demo.Demo2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcn/z3inc/demo/Demo2;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: iinc 1, 1
6: istore_1
7: return
LineNumberTable:
line 10: 0
line 11: 2
line 12: 7
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 args [Ljava/lang/String;
2 6 1 i I
}
SourceFile: "Demo2.java"
扩展命令,解压jar包命令:jar -xvf xxx.jar
② jclasslib插件:
jclasslib插件使用教程:https://www.bilibili.com/video/BV1Wd4y1M7tU
查看字节码内容之前,需要编译源代码:
③ Arthas: (Java应用诊断利器)
Arthas是一款线上监控诊断产品,通过全局视角实时查看应用load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,大大提升线上问题排查效率。
官网地址:https://arthas.aliyun.com/doc/
下载地址:https://arthas.aliyun.com/arthas-boot.jar
1、以jar包方式运行arthas: