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