【实战JVM】-基础篇-01-JVM通识-字节码详解-类的声明周期-加载器
- 1 初识JVM
- 1.1 什么是JVM
- 1.2 JVM的功能
- 1.2.1 即时编译
- 1.3 常见JVM
- 2 字节码文件详解
- 2.1 Java虚拟机的组成
- 2.2 字节码文件的组成
- 2.2.1 正确打开字节码文件
- 2.2.2 字节码组成
- 2.2.3 基础信息
- 2.2.3.1 魔数
- 2.2.3.1 主副版本号
- 2.2.4 常量池
- 2.2.5 方法
- 2.3 linux中打开字节码文件
- 2.4 字节码常用工具 Arthas
- 2.4.1 安装Arthas
- 2.4.2 Arthas功能
- 2.4.2.1 获取系统实时面板-dashboard
- 2.4.2.2 加载特定类的字节码-dump
- 2.4.2.3 反编译已加载类的源码-jad
- 2.4.2.4 查看JVM已加载的类信息-sc
1 初识JVM
1.1 什么是JVM
1.2 JVM的功能
1.2.1 即时编译
即时编译Just-In-Time 简称JIT进行性能的优化,最终到达接近C、C++的性能
将热点代码转换为机器码后保存至RAM,下次执行时直接从RAM中调用。
1.3 常见JVM
java -version
2 字节码文件详解
2.1 Java虚拟机的组成
2.2 字节码文件的组成
2.2.1 正确打开字节码文件
安装jclasslib
打开任意一个class文件
2.2.2 字节码组成
-
基础信息(一般信息+接口):
- 魔数、字节码对应的java版本号,访问标识(public、final等),以及这个类父类是哪个,以及实现了哪些接口
-
常量池:
- 保存了字符串常量、类、接口名、字段名。主要在字节码指令中使用。
-
字段:
-
当前类或接口的字段信息,包括名字,描述符,访问标识。
-
private final static int a1=0
-
-
方法:
- 当前类或接口的声明的方法信息字节码指令
-
属性:
- 类的属性,比如源码的名字、内部类的列表等
2.2.3 基础信息
2.2.3.1 魔数
打开二进制的png文件,就是以89504E47开始的
jpg则以FFD8FF开始
java字节码则是以CAFEBABE开始
2.2.3.1 主副版本号
52对应jdk1.8 61则对应jdk17
2.2.4 常量池
public class HelloWorld{
public static final String a1= "a1a1a1";
public static final String a2= "a1a1a1";
public static void main(String[] args){
System.out.println("Hello world!");
}
}
查看编译后的class文件
两个都是常量,且指向同一块常量值索引,CONSTANT_String_info存放着cp_info #33,依旧是个索引
查看cp_info #33,这时候字面量才是a1a1a1
为什么要两级索引呢?这是因为在jvm中的运行时数据区域中有这方法区,方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。
public class HelloWorld{
public static final String a1= "abc";
public static final String a2= "abc";
public static final String abc= "abc";
public static void main(String[] args){
System.out.println("Hello world!");
}
}
比如字段名和常量名都叫abc,但常量名是abc是String类型,而字段名是无类型的,但是都指向utf8格式的abc
2.2.5 方法
public static void main(String[] args){
int i=0;
i=i++;
System.out.println(i);
}
对应字节码:
0 iconst_0 //操作数栈: [] -> [0],将常量值0压入操作数栈。
1 istore_1 //操作数栈: [0] -> [],将操作数栈顶的整数值(0)存入本地变量1。
2 iload_1 //操作数栈: [] -> [0],将本地变量1中的整数值(0)加载到操作数栈。
3 iinc 1 by 1 //本地变量1的值增加1。原值是0,现在变为1。
6 istore_1 //操作数栈: [0] -> [],将操作数栈顶的整数值存入本地变量1。本地变量: [1]-> [0],
7 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;> //获取System.out的值并压入操作数栈。
10 iload_1 //操作数栈: [Ljava/io/PrintStream;] -> [Ljava/io/PrintStream;, 0],将本地变量1中的整数值加载到操作数栈。
11 invokevirtual #3 <java/io/PrintStream.println : (I)V>
14 return
i处于局部变量表的1号位
如果换成++i
public static void main(String[] args){
int i=0;
i=++i;
System.out.println(i);
}
0 iconst_0
1 istore_1 //0放到本地变量表
2 iinc 1 by 1 //本地变量表先自增,0->1
5 iload_1 //将本地变量1中的整数值(1)加载到操作数栈。
6 istore_1 //操作数栈: [1] -> [],将操作数栈顶的整数值存入本地变量1。本地变量: [1]-> [1],
7 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
10 iload_1
11 invokevirtual #3 <java/io/PrintStream.println : (I)V>
14 return
这样就不会发生i=i++这种覆盖赋值的情况了。
作业:
int i=0,j=0,k=0;
i++;
j=j+1;
k+=1;
i和k一样快,都是把0从操作数栈中放入本地变量中直接操作本地变量自增。
j最慢,从本地变量表中加载到操作数栈中,再加载1,再相加,再放入本地变量表。
2.3 linux中打开字节码文件
javap -v 字节码文件名称
2.4 字节码常用工具 Arthas
2.4.1 安装Arthas
启动arthas
先启动项目再分析
public class ArthasDemo {
public static void main(String[] args) {
while (true) {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在arthas工作目录中启动
java -Dfile.encoding=UTF-8 -jar arthas-boot.jar
-Dfile.encoding=UTF-8
是让arthas以utf8格式启动,这样不会乱码
成功捕获到ArthasDemo,选择3回车进入arthas内部,他还自动下载了arthas3.7.2版本
3
2.4.2 Arthas功能
2.4.2.1 获取系统实时面板-dashboard
我们设置每隔两秒刷新一次,刷新3次
dashboard -i 2000 -n 3
只显示1次
2.4.2.2 加载特定类的字节码-dump
dump -d D:/File/StudyJavaFile/JavaStudy/JVM/low/day01/resource/ com.sjb.arthas.ArthasDemo
这样就获取了运行时的java文件的字节码信息
2.4.2.3 反编译已加载类的源码-jad
jad com.sjb.arthas.ArthasDemo
和我们的源码几乎一致
案例
启动springboot-classfile后
public UserVO user(@PathVariable("type") Integer type,@PathVariable("id") Integer id){
//前边有一大堆逻辑,巴拉巴拉
if(type==UserType.REGULAR.getType()){
return new UserVO(id,"普通用户无权限查看");
}
return new UserVO(id,"这是尊贵的收费用户才能看的秘密!");
}
不能用==来判断类型,需要equals
即使是普通用户,但是因为用的==判断的类型,也能进入vip用户
使用jad查看
jad com.itheima.springbootclassfile.controller.UserController
定位到问题信息,以供以后热更新
2.4.2.4 查看JVM已加载的类信息-sc
sc -d 类名(java.lang.String)
查看当前类的类加载器,如果为空,则为启动类加载器。