分析字节码文件泛解析工具
- javap jdk自带泛解析.class文件为可读格式
javap使用例子: javap -p -v HelloWorld.class: 打印该字节码文件私有变量及更多信息 javac中可以指定额外内容输出到字节码,常用的如下: javac-g:lines 强制生成LineNumberTable javac-g:vars 强制生成LocalVariableTable Javac-g 生成所有debug信息
- jclasslib 图形化工具,更直观查看字节码,分门别类的对类中各部分进行整理,可以将其插件与idea开发工具集成
jclasslib下载地址:https://github.com/ingokegel/jclasslib
- 简单例子
class B{ private int a = 1234; static long C = 1111; public long test(long num){ long ret = this.a +num + C; } }public class A{ private B b = new B(); public static void main(String[] args){ Aa= new A(); long num = 4321; long ret = a.b.test(num); System.out.println(ret); } }
- 类的初始化发生在类加载阶段,除了new,还有如下方式
1.class的newIntance方法 ,会通过反射机制调用构造器 2.Constructor类的newInstance方法,会调用构造器 3.反序列化,不调用构造器 4.使用Object的clone方法
- 类加载和对象创建流程
当虚拟机遇到一条new指令是,首先会检查该指令参数能否在(运行时)常量池中定位一个符号引用。然后检查该符号引用的类字节码是否加载、解析和初始化。如果没有,将执行对应的类加载过程. 如上面例子:A的对象a调用b.test()时,会触发 B 类加载 A和B会被加载到元空间的方法区,进入main方法后,讲给执行引擎执行。该执行过程在栈上完成,其中有几个重要区域:虚拟机栈,程序计数器等
- 执行A代码,调用private B b = new B(),就会触发B类加载
文章图片
- 编译A.java生成字节码(如用idea,可以直接将参数追加到VM options里面)
强制生成LIneNumberTable,LocalVariableTab javac -g:lines -g:vars A.java
- 查看字节码
使用javap查看A.java和B.java的字节码,输出行号,本地变量表信息,还会输出当前类用到的常量池信息 java -p -v A Javap -p -v B
- 字节码形式
1:invokespecial #1// Method java/lang/Object."
":()V 2:#2 = Fieldref#6.#27// B.a:I 3:#6 = Class#29// B 4: #27 = NameAndType#8:#9// a:I 5:#8 = Utf8a 6:#9 = Utf8I 可以看到第一行,对象初始化首先调用Object.init()方法,而不是 第二行是拼接了# 13 和#14的内容
- 字节码特殊字符
基本类型:B-byteC-char D-double F-float I-intJ-long S-shortZ-boolean 特殊类型:V - void 数组类型(每一位都是要一个前置”[“字符来描述): [Ljava/lang/String;
- jclasslib 查看常量池(内容存放于Metaspace区域,属于堆外内存)
常量池包含.class文件常量池,运行时常量池,String常量池,大多是一些静态内容
文章图片
- 查看类和对象初始化方法,包含上面例子test方法代码区域
本地变量表的slot可复用。注意其index最大值为3,证明本地变量表最多同时能够存放4个变量 另外观察LineNumberTable选项。该属性的作用是描述源码行号和字节码行号(偏移量)对应关系,有了这些信息,在debug是,就能够获取发生异常代码源代码行号
文章图片
例子中test函数执行过程 code区域解释
- 变量、参数
同时使用了成员变量a,静态变量C,输入参数num。内存是在虚拟机栈分配,方法如下 public long test(long num){ long ret = this.a +num + C; return ret; }
- 对应字节码
public long test(long); descriptor:(J)J flag:ACC_PUBLIC Code: stack=4(栈帧最大深度为4),locals=5,args_size=2(有个默认参数this) 0: aload_0- 将第一个引用型局部变量推到操作数栈 1: getfield #2//Field a:I- 获取第二个属性 4: i2l-将整型转为长整型 5: lload_1 6: ladd 7: getstatic #3//Field C:J-获取第三个属性(c的类型为long) 10: ladd 11: lstore_3 - 存储第三个属性变量ret 12:lload_3 -加载第三个属性ret 13: lreturn -返回ret LineNumberTable: line 13:0 line 14:12 LocalVariableTable: StartLengthSlot NameSiagnature 0140thisLB; 0141numJ 1223retJ
- 重要参数
上面stack字样 为4 (stack=4):表明test方法最大操作数栈深度为4,jvm运行时根据该数值来分配栈帧中操作栈的深度 locals变量存储量局部变量的存储空间。单位是slot(槽),可被重用。其中存放内容包括:this,方法参数,异常处理器参数,方法体中定义的局部变量 Arg_size为方法参数个数,每个方法都有隐藏参数this,故为2
- 0: aload_0
将第 1 个引用型局部变量推到操作数栈,把this压入操作数栈 对于static方法,aload_0表述对方法第一个参数(本方法为隐藏参数this,为第一个参数)
- 【java|JVM之字节码如何在jvm流转】1: getfield #2
将站定指定的对象的第2个实例域(Field)的值,压入栈顶。#2指成员变量a. #2 = Fieldref#6.#27// B.a:I ... #6 = Class#29// B #27 = NameAndType#8:#9// a:I
- i2l
将栈顶int类型数据转换为long类型,设计隐式类型转换,图中信息无变动
- lload_1
将第一个局部变量(num)入栈.这里l表示long,同样是局部变量装载。会看到该位置局部变量一开始就已经有值
- ladd
把栈顶两个long类型值出栈后相加,并将结果入栈
- getstatic #3
根据偏移量获取静态属性值,并把该值push到操作数栈
- ladd
再次咨询ladd
- lstore_3
把栈顶logn型数值存入第4个局部变量(ret) slot为4,索引为3的就是ret变量
- lload_3
与上面想法。上面变量存入,现在把该变量玉如虚拟机栈中
- lreturn
从当前方法返回long类的ret变量
[字节码指令列表](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html)
test方法改动为直接return this.a+num+c
````
public long test(long num) {
return this.a + num + C;
}
````
- 字节码指令
0:aload_0-加载第一个参数this 1:getfield #2 //Field a:I-第二个参数对象a入栈 4:i2l - int转为long 5:lload_1 -加载第第一个局部变量入栈(num) 6:ladd - 将栈顶两个long变量相加 7:getstatic #2 //Field C:J-根据偏移量获取静态属性值,并把该值push到操作数栈 10:ladd -再次将栈顶两个long型变量相加 11:lreturn-返回相加后的值
推荐阅读
- Java|Java基础——数组
- 人工智能|干货!人体姿态估计与运动预测
- java简介|Java是什么(Java能用来干什么?)
- Java|规范的打印日志
- Linux|109 个实用 shell 脚本
- 程序员|【高级Java架构师系统学习】毕业一年萌新的Java大厂面经,最新整理
- Spring注解驱动第十讲--@Autowired使用
- SqlServer|sql server的UPDLOCK、HOLDLOCK试验
- jvm|【JVM】JVM08(java内存模型解析[JMM])
- 技术|为参加2021年蓝桥杯Java软件开发大学B组细心整理常见基础知识、搜索和常用算法解析例题(持续更新...)