深入解析“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
桥方法可能会变得十分奇怪。假设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;
}
字节码(缩减版):
