【问题标题】:How does optimization of final references work in java?最终引用的优化如何在 java 中工作?
【发布时间】:2013-03-25 11:13:48
【问题描述】:

我一直试图弄清楚 Java 优化的所有内容,发现了一些有趣的东西。

第一种情况:原始类型编译时优化

public class Clazz {
    public static void main(String args[]) {
        final int i = 300;
        new Clazz() {
            void foo() {
                System.out.println(i);
            }
        }.foo();
    }
}

编译后(我用jd-gui-0.3.5.windows反编译二进制文件)是这样的:

public class Clazz {
    public static void main(String[] args) {
        int i = 300;
        new Clazz() {
            void foo() {
                System.out.println(300);
            }
        }.foo();
    }
}

正如预期的那样,不是吗? i 在编译后被替换为它的值(内联优化)。所以,我希望在用它的包装器替换原始类型后看到类似的东西,但是......

第二种情况:非原始类型编译时优化

public class Clazz {
    public static void main(String args[]) {
        final Integer i = 300; // replaced int with Integer
        new Clazz() {
            void foo() {
                System.out.println(i);
            }
        }.foo();
    }
}

编译后:

public class Clazz {
    public static void main(String[] args) {
        Integer i = Integer.valueOf(300);
        new Clazz() {
            void foo() {
                System.out.println(Clazz.this);
            }
        }.foo();
    }
}

问题:

在这种情况下Clazz.this 是什么?我知道,它引用了Clazz 的封闭实例,但在这种情况下它不应该工作!我需要打印i,但编译器建议我打印Clazz.this 而不是它,它可以工作!问题是什么? jd-gui 是否会错误地反编译或者我是否遗漏了一些关于 Java 编译和优化的内容?

UPD:

Class$1的内容:

class Clazz$1 extends Clazz {
    Clazz$1(Integer paramInteger) {}

    void foo() {
        System.out.println(this.val$i);
    }
}

【问题讨论】:

  • 能把Clazz$1的反编译码也放上来吗?这可能是System.out.println(Clazz.this) 的线索。因为Clazz$1 可能包含返回itoString() 方法
  • 请把clazz$1的代码也放上来。!
  • @VishalK jd-gui 不会单独显示匿名类,它会将其重新包含在封闭类中。
  • @assylias 实际上显示了
  • @zvzdhk 啊我明白了——我直接查看生成的 jar,在这种情况下 jd-gui 重新注入匿名类——如果你直接查看类文件,你就会得到你发布的内容。跨度>

标签: java optimization compilation


【解决方案1】:

jd-gui 是否会错误地反编译,或者我是否遗漏了一些关于 Java 编译和优化的内容?

jd-gui 反编译代码不正确。

在我的 JVM 上,匿名类的反汇编代码如下所示:

class Clazz$1 extends Clazz {
  Clazz$1(java.lang.Integer);
    Code:
       0: aload_0       
       1: aload_1       
       2: putfield      #10                 // Field val$i:Ljava/lang/Integer;
       5: aload_0       
       6: invokespecial #12                 // Method Clazz."<init>":()V
       9: return        

  void foo();
    Code:
       0: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0       
       4: getfield      #10                 // Field val$i:Ljava/lang/Integer;
       7: invokevirtual #26                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      10: return        
}

如您所见,i 的副本存储在匿名类中,位于名为 val$i 的字段中(名称是特定于实现的)。

正是这个字段,您的反编译器似乎错误地呈现为Clazz.this

【讨论】:

  • 这应该是最接近的方法,jd-gui 错过了在调用System.out.println 时解释Integer 对象如何将其值装箱。反编译算法本身必须假定对这个 Integer 对象最重要的引用是什么,并选择 Clazz.this 的结果。
【解决方案2】:

您可以简单地看一下字节码 (javap -c Clazz$1.class)。

int i = 300:

void foo();
   0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
   3: sipush        300
   6: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
   9: return

Integer i = 300:

void foo();
   0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
   3: aload_0
   4: getfield      #1                  // Field val$i:Ljava/lang/Integer;
   7: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
  10: return

所以 int 是内联的,但 Integer 不是。


另外记录一下,这是我从 jd-gui (3.0.5) 得到的:

public static void main(String[] args) {
  Integer i = Integer.valueOf(300);
  new Clazz() {
    void foo() {
      System.out.println(this.val$i);
    }
  }
  .foo();
}

【讨论】:

    【解决方案3】:

    这应该是最接近的方法,jd-gui 错过了在调用System.out.println 时解释Integer 对象如何将其值装箱。反编译算法本身必须假定对这个Integer 对象最重要的引用是什么,它会选择Clazz.this 的结果。

    【讨论】:

      猜你喜欢
      • 2017-01-24
      • 1970-01-01
      • 1970-01-01
      • 2017-09-18
      • 1970-01-01
      • 2012-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多