JVM之字节码如何在jvm流转

分析字节码文件泛解析工具

  • 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){
    A  a= 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类加载
    JVM之字节码如何在jvm流转_第1张图片

查看字节码

命令行查看字节码

  • 编译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 = Utf8               a
    6:   #9 = Utf8               I
    
     可以看到第一行,对象初始化首先调用Object.init()方法,而不是
    第二行是拼接了# 13 和#14的内容
    
  • 字节码特殊字符

    基本类型:B-byte  C-char D-double F-float I-int  J-long S-short  Z-boolean
    特殊类型:V - void
    数组类型(每一位都是要一个前置”[“字符来描述): [Ljava/lang/String; 
    

可视化查看字节码

  • jclasslib 查看常量池(内容存放于Metaspace区域,属于堆外内存)
常量池包含.class文件常量池,运行时常量池,String常量池,大多是一些静态内容

JVM之字节码如何在jvm流转_第2张图片

  • 查看类和对象初始化方法,包含上面例子test方法代码区域
    本地变量表的slot可复用。注意其index最大值为3,证明本地变量表最多同时能够存放4个变量
    另外观察LineNumberTable选项。该属性的作用是描述源码行号和字节码行号(偏移量)对应关系,有了这些信息,在debug是,就能够获取发生异常代码源代码行号
    

JVM之字节码如何在jvm流转_第3张图片

例子中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:
    			Start 	Length 	Slot Name 	Siagnature
    			 0      14     0  this   LB;
    			 0      14     1   num   J
    			12       2     3   ret   J
    
  • 重要参数
    上面stack字样 为4 (stack=4):表明test方法最大操作数栈深度为4,jvm运行时根据该数值来分配栈帧中操作栈的深度
    locals变量存储量局部变量的存储空间。单位是slot(槽),可被重用。其中存放内容包括:this,方法参数,异常处理器参数,方法体中定义的局部变量
    Arg_size为方法参数个数,每个方法都有隐藏参数this,故为2
    

字节码执行流程及含义

  • 0: aload_0

    将第 1 个引用型局部变量推到操作数栈,把this压入操作数栈
    对于static方法,aload_0表述对方法第一个参数(本方法为隐藏参数this,为第一个参数)
     
    
  • 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,jvm)