根据这篇文章http://slurp.doc.ic.ac.uk/pubs/observing/linking.html#assignment:
由于 Java 代码和字节码之间的信息差异(字节码不包含局部变量的类型),验证者不需要检查子类型是否分配给局部变量或参数。
我的问题:为什么字节码不包含局部变量的类型信息,而它确实包含参数和返回值的类型信息?
根据这篇文章http://slurp.doc.ic.ac.uk/pubs/observing/linking.html#assignment:
由于 Java 代码和字节码之间的信息差异(字节码不包含局部变量的类型),验证者不需要检查子类型是否分配给局部变量或参数。
我的问题:为什么字节码不包含局部变量的类型信息,而它确实包含参数和返回值的类型信息?
首先,有几种不同的类型概念。有编译时类型,其中包括泛型。但是,泛型在编译后不存在。
有验证推断变量的静态类型,可以是 int、float、long、double、returnaddress 或对象引用。对象引用附加了一个上限类型,因此所有引用都是java/lang/String
例如的子类型。字段还可以具有以下一种短类型:byte、short、char 或 boolean。出于执行目的,它们与 int 相同,但存储不同。
最后是运行时类型,它与验证的静态类型相同,但在对象引用的情况下,表示被引用实例的实际类型。请注意,由于验证者的惰性,在某些情况下运行时类型实际上可能不是已验证类型的子类型。例如,声明类型的变量Comparable
实际上可以保存 Hotspot 中的任何对象,因为 VM 在验证时不检查接口。
编译时间信息不会保留,除非通过反射和调试的可选属性。这是因为没有理由保留它。
局部变量没有明确的类型信息(新的 StackMapTable 属性除外,但这是技术性的)。相反,当加载类时,字节码验证器通过运行静态数据流分析来推断每个值的类型。这样做的目的不是为了捕捉编译时类型检查之类的错误,因为假设字节码在编译时已经通过了此类检查。
相反,验证的目的是确保指令不会对 VM 本身造成危险。例如,它需要确保您没有采用整数并将其作为对象引用进行干预,因为这可能导致任意内存访问和入侵 VM。
因此,虽然字节码值没有显式类型信息,但它们确实具有隐式类型,这是静态类型推断的结果。这个细节根据每个 VM 的内部实现细节而有所不同,尽管它们应该遵循 JVM 标准。但是您只需要担心手写字节码中的这一点。
字段具有明确的类型,因为 VM 需要知道其中存储了哪种类型的数据。方法参数和返回类型被编码在所谓的方法描述符中,也用于类型检查。它们不可能自动推断,因为这些值可以来自或去任何地方,而类型检查是基于每个类完成的。
PS 在谈到验证类型时,我遗漏了一些小细节。对象类型还跟踪它们是否已初始化,如果未初始化,则由哪个指令创建它们。地址类型跟踪创建它们的 jsr 的目标。
那是一张很旧的纸。当前的类文件确实包括本地和堆栈变量的类型。类型不存储在方法字节码中,而是存储在附加到方法的StackMapTable
属性中。
在没有 的情况下,可以(并且总是)通过数据流分析来重建所有局部变量和堆栈元素的类型StackMapTable
,但计算成本很高。StackMapTable
可以更快地验证带有 s 的代码。尽管我不得不承认,我看不出验证StackMapTable
s 比进行分析更快,但我对此几乎一无所知。
Javabytecode
保留了关于 的类型信息fields
,
但正如您所问method
returns
的,parameters
它不
包含 的类型信息。local variables
Java 类文件中的类型信息使得反编译的任务bytecode
比反编译
machine code
. 因此,反编译 Java 字节码需要分析大多数局部变量类型、扁平化基于堆栈的指令以及 和 的loops
结构conditionals
。然而,字节码反编译的任务比编译要困难得多。您会经常看到反编译器无法完全执行其预期功能