深入解析“Java 字节码 ” 之 「从案例解读虚拟机属性表」

Posted by tomas家的小拨浪鼓 on November 3, 2019

深入解析“Java 字节码 ” 之 「从案例解读虚拟机属性表」

本文主要是《深入探索 JVM》系列中『字节码篇』文章,主要是通过实例对字节码的属性表进行一个直观的介绍。系列文章目录见:《 深入探索 JVM 》文集


『字节码』篇文章推荐:
深入解析“Java 字节码 ” 之 「类文件结构」
深入解析“Java 字节码 ” 之 「从案例深度解读 Java 字节码」
深入解析“Java 字节码 ” 之 「进一步探究 Java 方法的字节码实现」
深入解析“Java 字节码 ” 之 「从案例解读虚拟机属性表」
深入解析“Java 字节码 ” 之 「虚拟机字节码执行引擎」
深入解析“Java 字节码 ” 之 「动态代理的实现」


一,前言

本文主要根据案例对虚拟机规范定义的属性有更直观的了解

1.1 虚拟机规范定义的属性

属性名称 使用位置 含义
Code 方法表 Java 代码编译成的字节码指令
ConstantValue 字段表 final 关键字定义的常量值
Deprecated 类、方法表、字段表 被声明为 deprecated 的方法和字段
Exceptions 方法表 方法抛出的异常
EnclosingMethod 类文件 仅当一个类为局部类或匿名类时才能拥有这个属性,这个属性用于标识这个类所在的外围方法
InnerClasses 类文件 内部类列表
LineNumberTable Code 属性 Java 源码的行号与字节码指令的对应关系
LocalVariableTable Code 属性 方法的局部变量表
StackMapTable Code 属性 JDK 1.6 中新增的属性,供新的类型检查验证器(Type Checker)检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配
Signature 类、方法表、字段表 JDK 1.5 中新增的属性,这个属性用于支持泛型情况下的方法签名,在 Java 语言中,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数化类型(Parameterized Types),则 Signature 属性会为它记录泛型签名信息。由于 Java 的泛型采用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的相关信息
SourceFile 类文件 记录源文件名称
SourceDebugExtension 类文件 JDK 1.6 中新增的属性,SourceDebugExtension 属性用于存储额外的调试信息。譬如在进行 JPS 文件调试时,无法通过 Java 堆栈来定位到 JSP 文件的行号,JSR-45 规范为这些非 Java 语言编写,却需要编译成字节码并运行在 Java 虚拟机中的程序提供了一个进行调试的标准机制,使用 SourceDebugExtension 属性就可以用于存储这个标准所新加入的调试信息
Synthetic 类、方法表、字段表 标识方法或字段为编译器自动生成的
LocalVariableType JDK 1.5 中新增的属性,它使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
RuntimeVisibleAnnotations 类、方法表、字段表 JDK 1.5 中新增的属性,为动态注解提供支持。RuntimeVisibleAnnotations 属性用于指明哪些注解是运行时(实际上运行时就是进行反射调用)可见的
RuntimeInvisibleAnnotations 类、方法表、字段表 JDK 1.5 中新增的属性,与 RuntimeVisibleAnnotations 属性作用刚好相反,用于指明哪些注解是运行时不可见的
RuntimeVisibleParameterAnnotations 方法表 JDK 1.5 中新增的属性,作用与 RuntimeVisibleAnnotations 属性类似,只不过作用对象为方法参数
RuntimeInvisibleParameterAnnotations 方法表 JDK 1.5 中新增的属性,作用与 RuntimeInvisibleAnnotations 属性类似,只不过作用对象为方法参数
AnnotationDefault 方法表 JDK 1.5 中新增的属性,用于记录注解类元素的默认值
BootstrapMethods 类文件 JDK 1.7 中新增的属性,用于保存 invokedynamic 指令引用的引导方法限定符

除了前面已经见到过的「Code」、「Exceptions」、「LineNumberTable」、「LocalVariableTable」、「SourceFile」属性外。本文将通过示例来继续了解「ConstantValue」、「Deprecated」、「EnclosingMethod」、「InnerClasses」、「Signature」、「RuntimeVisibleAnnotations」、「RuntimeInvisibleAnnotations」、「RuntimeVisibleParameterAnnotations」、「RuntimeInvisibleParameterAnnotations」、「AnnotationDefault」这些属性。


二,案例一

「ConstantValue」、「Deprecated」、「EnclosingMethod」、「InnerClasses」

// 属性:InnerClasses
public class AttributeFlagDemo {
    // 属性:ConstantValue
    final String str = "hello";
    // 属性:Deprecated、RuntimeVisibleAnnotations
    @Deprecated
    Integer age;
    // 属性:Deprecated、RuntimeVisibleAnnotations
    @Deprecated
    public void oldMethod() {
        System.out.println("old method");
    }
    public void anonymityClassMethod() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread run");
            }
        }).start();
    }
}

编译后生成了 2 个 class 文件:

➜  classes git:(master) ✗ ll com/bayern/shengsiyuan/jvm_lecture/linling/
total 16
-rw-r--r--  1 linling  staff   942B Oct 30 20:19 AttributeFlagDemo$1.class
-rw-r--r--  1 linling  staff   1.1K Oct 30 20:19 AttributeFlagDemo.class
  • 「AttributeFlagDemo.class」文件字节码(缩减版):

  • 「AttributeFlagDemo$1.class」字节码(缩减版):


三,案例二 ———— 泛型

# DateInterval 
public class DateInterval extends Pair<Date> {
    @Override
    public void setSecond(Date second) {
        if (second.compareTo(getFirst()) >= 0) {
            super.setSecond(second);
        }
    }
    @Override
    public Date getSecond() {
        return super.getSecond();
    }
    public static void main(String[] args) {
        DateInterval dateInterval = new DateInterval();
    }
}

# Pair 
public class Pair<T> {
    private T first;
    private T second;
    public Pair() {}
    private Pair(T first, T second) {
        this.first = first;
        this.second = second;
    }
    public T getFirst() {
        return first;
    }
    public void setFirst(T first) {
        this.first = first;
    }
    public T getSecond() {
        return second;
    }
    public void setSecond(T second) {
        this.second = second;
    }
}


字节码(缩减版):

注意,这里字节码文件中出现了两个「setSecond」和「getSecond」方法!! 这里DateInterval 存在另一个从Pair继承的setSecond方法,即:

public void setSecond(Object second);    // Pair类在类型擦除后,setSecond 方法的参数类型就是 Object

这显然是一个不同的方法,因为它有一个不同类型的参数 ———— Object,而不是Date。然而,不应该不一样。
考虑下面的语句序列:

DateInterval interval = new DateInterval(...);
Pair<Date> pair = interval; //OK — assigment to superclass
pair.setSecond(aDate);

这里,希望对setSecond的调用具有多态性,并调用最合适的那个方法。由于pair引用DateInterval对象,所以应该调用DateInterval.setSecond。问题在于类型擦除与多态发生了冲突。 要解决这个问题,就需要编译器在 DateInterval 类中生成一个桥方法(bridge method):

public void setSecond(Object second) { 
    setSecond((Date)second);
}

想要了解它的工作过程,请仔细地跟踪下列语句的执行:

pair.setSecond(Date);

变量pair已经声明为类型Pair,并且这个类型只有一个简单的方法叫setSecond,即setSecond(Object)。虚拟机用pair引用的对象调用这个方法。这个对象是DateInterval类型的,因而将会调用DateInterval.setSecond( Object )方法。这个方法是合成的桥方法。它调用DateInterval.setSecond(Date),这正是我们所期望的操作效果。

桥方法可能会变得十分奇怪。假设DateInterval也覆盖了getSecond方法:

public Date getSecond()
{
     return (Date)super.getSecond().clone();
}

在擦除的类型中,有两个getSecond方法:

Date getSecond() // defined in DateInterval
Object getSecond() // defind in Pair

不能这样编写Java代码(在这里,具有相同参数类型的两个方法是不合法的)。它们都没有参数。但是,在虚拟机中,用参数类型和返回类型确定一个方法。因此编译器可能产生两个仅返回类型不同的方法字节码,虚拟机能够正确地处理这一情况。


四,案例三 ———— 注解

例一

public class AnnotationDemo {
    @AnnotationOne
    public String strOne;
    @AnnotationTwo
    public String strTwo;
    public void methodOne(@AnnotationThree String strThree) {}
    
    public void methodFour(@AnnotationFour String strFour) {}
    public static void main(String[] args) {
        AnnotationDemo demo = new AnnotationDemo();
    }
}

@Documented
@Retention(RetentionPolicy.CLASS)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
@interface AnnotationOne {
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
@interface AnnotationTwo {
}

@Documented
@Retention(RetentionPolicy.CLASS)
@Target(value={PARAMETER})
@interface AnnotationThree {
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={PARAMETER})
@interface AnnotationFour {
}

字节码(缩减版):


例二

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={FIELD})
@interface AnnotationDf {
    int value() default 25;
}

字节码(缩减版):