【问题标题】:Why bytecode calls Object->getClass() at a direct field access为什么字节码在直接字段访问中调用 Object->getClass()
【发布时间】:2012-10-29 04:20:32
【问题描述】:

我反编译了 Java(实际上是 Dalvik)字节码。在方法的开头,我直接访问实例成员的字段(即不通过 getter)。

似乎 Java 在访问的实例成员 (mOther) 上调用了 Object.getClass(),但没有在任何地方使用结果。这是某种检查吗?为什么需要这个调用?我怀疑这是因为我直接访问了一个字段(在该类中定义),但我没有看到连接。

Java代码和反编译的字节码如下。

(请注意,最后一条指令将lifeTime 加载为常量0x0001,因为在MyOtherClass 中,我将lifeTime 作为public final 字段,并且当前是从代码初始化的。)

MyOtherClass other = mOther;
if (mAge >= other.lifeTime) { // lifeTime is initialized to 0x0001
   end();
   return;
}

.line 53
move-object/from16 v0, p0
iget-object v0, v0, Lcom/example/engine/MyClass1;->mOther:Lcom/example/engine/MyOtherClass;
move-object/from16 v16, v0

.line 54
.local v16, other:Lcom/example/engine/MyOtherClass;
move-object/from16 v0, p0

iget v0, v0, Lcom/example/engine/MyClass1;->mAge:I
move/from16 v18, v0

// Why is Object->getClass() called?
invoke-virtual/range {v16 .. v16}, Ljava/lang/Object;->getClass()Ljava/lang/Class;

const/16 v19, 0x0001

更新:

在 cmets 中要求我提供该方法的完整源代码。请注意,mOther 是 final 字段(出于性能原因)。你在这里:

@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; }
    }
}

【问题讨论】:

  • 你能提供你正在反编译的函数的完整源代码,包括它的声明吗?我认为您忽略了一些对您的问题很重要的细节。
  • @danfuzz ha。你打败了我:)
  • Hrm,还不清楚(抱歉)。你能包含函数的其余反编译吗?更好的是,也许你可以减少函数(例如,去掉循环,甚至可能去掉一个 if 语句),然后反编译 that。可能还有你感兴趣的好奇心。
  • 请注意,它是 Dalvik VM,而不是 JVM。我正在为 Android 2.2(及更高版本)开发。我正在为 DEX 文件使用流行的 baksmali 反汇编程序。今天,我将尝试迭代地缩减函数,并检查它如何更改反编译的字节码(以及 getClass() 是否仍然存在)。
  • @ThomasCalc 是的,我知道这是 Dalvik。 (注:我设计了 Dalvik。)

标签: java android bytecode dalvik


【解决方案1】:

我假设 lifeTime 是在声明时分配的最终字段:

 final int lifeTime = 0x0001;

如果是这样,字节码会通过以下方式进行优化(它几乎与 VM 无关,纯编译器魔法):

  • 无需真正从内存中获取数据:只需加载一个常量 1。
  • 但是如果字段的所有者恰好是 null 怎么办?在这种情况下,必须抛出 NullPointerException。为了保证这样的行为,编译器发出对 getClass() 的调用,因为
    • 实际上检查 null,构造一个新的 NullPointerException 实例并抛出它是很多更多的字节码,
    • 此类调用在 VM 中进行了非常优化,
    • 此方法始终可用,
    • 它不需要任何参数。

一个更简单的例子:

class Test {
    private final int myFinalField = 1;

    int test(Test t) {
        return t.myFinalField;
    }
}

如果我们看一下 test() 方法的字节码(这次是 JVM,但如果你把它翻译成 Dalvik,它本质上是一样的),这里也是对 getClass() 的调用:

 // 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

【讨论】:

    【解决方案2】:

    Andrey 的回答给出了这个问题的具体答案。但这里有一些与此类问题相关的注意事项:

    1. 显然,您可以在 Oracle / OpenJDK 工具链生成的字节码中看到类似的情况。这并不奇怪,因为 Davlik 字节码的一些生成路径涉及将 Java 源代码编译为 JVM 字节码,然后将它们转换为 Davlik 字节码。

    2. 如果您因为查看字节码以深入了解某些代码的性能而遇到这个奇怪的工件,那么您可能找错地方了。在现代 JVM / Davlik / ART 引擎中,字节码被翻译成本机代码,而本机代码是大多数或所有时间执行的代码1

      要更可靠地了解“微观”级别的代码性能,您需要检查 AOT 或 JIT 编译器生成的本机代码。

    3. 字节码编译器发出的字节码通常没有经过大量优化的原因之一是,这样做可能会使 AOT/JIT 更难以有效优化。

    4. Davlik 已被 ART 取代。


    1 - 对于 Hotspot JVM,仅支持 JIT 和直接字节码解释。 Davlik 的早期版本是只解释的,然后添加并改进了 JIT 支持。在 ART 中,以某种形式支持所有三种模式:解释、JIT 和 AOT。

    【讨论】:

      猜你喜欢
      • 2014-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-21
      • 2010-12-05
      • 1970-01-01
      相关资源
      最近更新 更多