字节码分析

类的字节码分析
//类的定义
class B {
    public static A a = new A();
    B(A a) {
        this.a = a;
    }
}

类的字节码如下:

// class version 49.0 (49)
// access flags 0x20
class src/java/B {
  // compiled from: Main.java
  // access flags 0x9
  public static Lsrc/java/A; a

  // access flags 0x0
  <init>(Lsrc/java/A;)V
   L0
    LINENUMBER 8 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 9 L1
    ALOAD 0
    POP
    ALOAD 1
    PUTSTATIC src/java/B.a : Lsrc/java/A;
   L2
    LINENUMBER 10 L2
    RETURN
   L3
    LOCALVARIABLE this Lsrc/java/B; L0 L3 0
    LOCALVARIABLE a Lsrc/java/A; L0 L3 1
    MAXSTACK = 1
    MAXLOCALS = 2

  // access flags 0x8
  static <clinit>()V
   L0
    LINENUMBER 7 L0
    NEW src/java/A
    DUP
    INVOKESPECIAL src/java/A.<init> ()V
    PUTSTATIC src/java/B.a : Lsrc/java/A;
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 0
}

当调用B b = new B()的时候,字节码为:

NEW src/java/B

  1. 检查new指令的参数 src/java/B 是否能在常量池中定位到一个类的符号引用。
    class文件中除了有类的版本号,字段,方法,接口等描述信息外,还有一项信息是常量池表,用于存放编译期生成的各种字面量和符号引用(这里就包含了类的符号引用),在类加载之后进入方法区的运行时常量池。java虚拟机为每一个类型都维护着一个常量池。
  2. 检查这个符号引用代表的类是否已被加载,解析和初始化过。
  3. 如果没有,则先执行相应的类加载过程。

假设B没有初始化过。

这里补充一下类加载的过程:
加载,验证,准备,解析,初始化,使用,卸载
a) 加载:

a1) 通过一个类的全限定名获取定义此类的二进制字节流(src/java/ 目录下的B.class文件
a2) 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
a3) 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
a4) Class对象并没有明确规定是在java堆中,对于HotSpot虚拟机而言,Class对象比较特殊,它虽然是对象,但是存放在方法区内。

b)验证:

b1)文件格式验证
b2)元数据验证
b3)字节码验证
b4)符号引用验证 发生在解析阶段将符号引用转化为直接引用的时候

c)准备:

创建类和接口的静态字段,并用默认值初始化这些字段,这个阶段不会执行任何的虚拟机字节码指令,在初始化阶段,会有显示的初始化器来初始化这些静态字段。

d)解析

根据运行时常量池里的符号引用来动态决定具体值的过程,即实现符号引用到直接引用的转换。
解析过程的前提:java虚拟机指令anewarray,checkcast,getfield,putfield,getstatic,putstatic,invokestatic,instanceof,invokeddynamic,invokeinterface,invokespecial,invokevirtual,ldc,ldc_w,mulitianewarrat,new以上这些命令会将符号引用指向运行时常量池,执行任意一条指令都需要对它的符号引用进行解析。
d1) 类和接口的解析:解析D中对标记为N的类或接口C的未解析符号引用。
d2) 字段解析:解析从D指向类或接口C中某个字段的未解析符号引用,那么必须先解析指向该字段引用所提到的那个C的符号引用。
d3)普通方法解析:解析D中对类或接口C里某个方法的未解析符号引用,该方法引用所提到的对C的符号引用,就应该首先被解析

e)初始化:类或接口的初始化是指执行类或接口的初始化方法< clinit >方法

初始化的前提:
执行需要引用类或接口的java虚拟机指令时。new,getstatic,putstatic或invokestatic。这些指令会通过字段或方法来直接或间接引用某个类。
每个接口和类都有唯一的初始化锁。

假设B没有被初始化过,当加载完成后,B的二进制流文件被转化为方法区的运行时数据结构,并且在方法区里有一个B的Class对象。
符号引用验证阶段会判断符号引用能否找到被引用的类,这个校验发生在虚拟机将符号引用转化为直接引用的时候,即发生在解析阶段,所以,A类的加载是在B类的解析阶段加载的。

符号引用什么会解析成直接引用呢?
参考d)解析的几个命令

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值