考虑以下情况:
package packA;
public class A
{
private static int i = 0;
}
package packB;
public class B
{
public static void main(String[] args)
{
int i = A.i + 1;
}
}
由于A.i
是private
,因此无法从类B
访问它,这样做会导致编译错误。
但是,如果我按照这个B
类进行检测
public class B
{
public static void main(String[] args)
{
// getstatic packA/A.i : I
// iconst_1
// iadd
// istore_1
}
}
并调用main
方法,JVM验证程序是否会检查访问A.i
是否有效?同样,如果A.i
被声明为final
,它会将以下字节码传递为有效吗?
public class B
{
public static void main(String[] args)
{
// iconst_2
// putstatic packA/A.i : I
}
}
我想知道这个,因为验证者需要加载类来检查字段的访问标志。此外,如果它不验证它们,则可以检测恶意字节码以更改字段的值或提供允许访问字段而不反射的“黑客方法”。
答案 0 :(得分:1)
访问访问类不可见的字段会导致验证错误。为了应用此验证,JVM需要加载并链接声明此字段的类。这是正常的验证过程,例如,JVM还需要加载类以检查方法是否存在。然而,可以懒惰地应用验证和所需的类加载,即在第一次执行方法之前。
一个例外是HotSpot,其中一个类扩展了MagicAccessorImpl,这是一个内部接口。验证程序跳过此类的访问级别验证。这是JVM内部代码生成所必需的,其中反射代码被优化为字节代码。另一个例外是通过匿名类加载器加载的clases,它继承了另一个类可见性上下文,例如lambda类。
对于内部类,javac将包私有访问器方法插入到声明类中,以便内部类可以访问其外部类的字段。
编辑:我在评论后编辑了我的答案,但现在找到了验证这一点的时间,最后的字段确实与我的第一个答案略有不同,但也与评论所说的相比:
对于构造函数,验证程序仅验证是否在构造函数中执行了最终字段赋值,即字节代码级别上名为<init>
的方法。因此,在字节代码中可以使用以下内容,这是Java代码中不允许的:
class Foo {
final int bar; // is 0
Foo() { }
}
class Foo {
final int bar; // is 2
Foo() {
bar = 1;
bar = 2;
}
}
class Foo {
final int bar; // is 2
Foo() {
this(null);
bar = 2;
}
Foo(Void v) {
bar = 1;
}
}
对于静态字段,不对Java编译器强制实施此类限制,即以下Java代码在字节代码中是合法的:
class Foo {
static final int bar; // is 0
static { }
}
class Foo {
static final int bar; // is 2
static {
bar = 1;
bar = 2;
}
}
class Foo {
static final int bar; // is 2
static {
bar = 1;
foobar(2);
}
static foobar(int i) {
bar = i;
}
}
Foo.foobar(3); // bar is 3