Java 字节码为何调用对象->;直接访问字段时的getClass()
我反编译了Java(实际上是Dalvik)字节码。在方法的开头,我直接访问实例成员的字段(即,不通过getter) Java似乎在访问的实例成员(Java 字节码为何调用对象->;直接访问字段时的getClass(),java,android,bytecode,dalvik,Java,Android,Bytecode,Dalvik,我反编译了Java(实际上是Dalvik)字节码。在方法的开头,我直接访问实例成员的字段(即,不通过getter) Java似乎在访问的实例成员(mOther)上调用了Object.getClass(),但结果没有在任何地方使用。这是支票吗?为什么需要这个电话?我怀疑这是因为我直接访问了一个字段(该字段在该类中定义),但我没有看到连接 Java代码和反编译字节码如下所示 (请注意,最后一条指令将生存期加载为常量0x0001,因为在MyOtherClass中,我将生存期作为一个公共最终字段,并且当
mOther
)上调用了Object.getClass()
,但结果没有在任何地方使用。这是支票吗?为什么需要这个电话?我怀疑这是因为我直接访问了一个字段(该字段在该类中定义),但我没有看到连接
Java代码和反编译字节码如下所示
(请注意,最后一条指令将生存期
加载为常量0x0001
,因为在MyOtherClass
中,我将生存期
作为一个公共最终
字段,并且当前是从代码初始化的。)
更新:
评论中要求我提供该方法的完整源代码。请注意,mOther
是最后一个字段(出于性能原因)。给你:
@Override
public void doStep() {
MyOtherClass other = mOther;
if (mAge >= other.lifeTime) {
end();
return;
}
mAge += TICK_TIME;
boolean isSurrounded = false;
if (mAge > mLastSurroundTime + other.surroundingTime) {
int distance = (int)other.maxSurroundDistance;
for (int bx = bx0; bx <= bx1; ++bx) {
if (bx < 0 || bx >= mSize) { continue; }
for (int by = by0; by <= by1; ++by) {
if (by < 0 || by >= mSize) { continue; }
ArrayList<WorldObject> candidates = getCandidatesAtPos(bx, by);
for (int i = 0; i < candidates.size(); ++i) {
WorldObject obj = candidates.get(i);
if (mSelf!= obj && mSelf.getDistanceFrom(obj) <= other.maxSurroundDistance) {
obj.notifyDangerImminent(mSelf);
isSurrounded = true;
}
}
}
}
if (isSurrounded) { mLastSurroundTime = mAge; }
}
}
@覆盖
公共无效doStep(){
MyOtherClass其他=母亲;
如果(法师>=其他.寿命){
end();
返回;
}
法师+=滴答时间;
布尔值isSurrounded=false;
如果(mAge>mLastSurroundTime+other.surroundingTime){
int距离=(int)other.maxSurroundDistance;
对于(int bx=bx0;bx=mSize){continue;}
对于(int by=by0;by=mSize){continue;}
ArrayList候选者=getCandidatesAtPos(bx,by);
对于(int i=0;i 如果(mSelf!=obj&&mSelf.getDistanceFrom(obj)我假设lifeTime是在声明时分配的最终字段:
final int lifeTime = 0x0001;
如果是这样,字节码将按以下方式进行优化(它几乎与VM无关,纯粹是编译器的魔法):
- 不需要从内存中真正获取数据:只需要加载一个常量1
- 但是如果字段的所有者恰好是null,该怎么办?在这种情况下,必须抛出NullPointerException
- 实际上,检查null,构造一个新的NullPointerException实例并抛出它是很多更多字节码
- 这样的调用在虚拟机中非常优化
- 这种方法总是可用的
- 这不需要争论
一个简单的例子:
class Test {
private final int myFinalField = 1;
int test(Test t) {
return t.myFinalField;
}
}
如果我们看一下test()方法的字节码(这次是JVM,但如果您将其转换为Dalvik,它基本上是相同的),下面是对getClass()的调用:
Andrey的回答给出了这个问题的具体答案。但这里有一些与这类问题相关的注释:
显然,您可以在Oracle/OpenJDK工具链生成的字节码中看到类似的情况。这并不奇怪,因为Davlik字节码的某些生成路径涉及将Java源代码编译为JVM字节码,然后将其转换为Davlik字节码
如果您遇到这种奇怪的工件是因为您在查看字节码以了解某些代码的性能,那么您可能找错了地方。在现代JVM/Davlik/ART引擎中,字节码被转换为本机代码,而本机代码大部分或全部时间都在执行1
为了更可靠地了解“微”级别的代码性能,您需要检查AOT或JIT编译器生成的本机代码
字节码编译器发出的字节码通常不会进行大量优化,原因之一是这样做可能会使AOT/JIT更难有效优化
达夫利克已经被艺术所取代
1-使用Hotspot JVM,只支持JIT和直接字节码解释。Davlik的早期版本只支持解释,然后添加并改进了JIT支持。在ART中,所有三种模式都以某种形式支持:解释、JIT和AOT。您能否提供反编译函数的完整源代码,包括其declaration?我想你忽略了一些你问题中很重要的细节。@danfuzz ha。你比我强:)Hrm,仍然不清楚(对不起)。你能把函数反编译的其余部分也包括进去吗?更好的是,也许你可以缩减函数(例如,去掉循环,甚至一个if语句),并对其进行反编译。可能仍有您感兴趣的好奇心。请注意,这可能是Dalvik VM,而不是JVM。我正在为Android 2.2(及更高版本)开发。我正在使用流行的baksmali反汇编程序处理DEX文件。今天,我将尝试以迭代方式缩减函数,并检查它如何更改反编译的字节码(以及getClass()是否仍然保留)。@ThomasCalc Yep,我知道它是Dalvik。(注:我设计了Dalvik。)
class Test {
private final int myFinalField = 1;
int test(Test t) {
return t.myFinalField;
}
}
// access flags 0x0
test(LTest;)I
L0
LINENUMBER 5 L0
// load t
ALOAD 1
// if (t == null) throw new NullPointerException(); compressed in only two instructions
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
POP
// the actual value of myFinalField
ICONST_1
IRETURN
L1
LOCALVARIABLE this LTest; L0 L1 0
LOCALVARIABLE t LTest; L0 L1 1
MAXSTACK = 1
MAXLOCALS = 2