JVM/编译器优化对象未使用的属性

optimization of unused properties of object by JVM / Compiler

我的 class 包含一些从未在任何地方使用过的属性(这是我真实场景的 DEMO)。我听说 JVM 优化了我们的 Java 代码。

JVM/编译器是否优化/删除对象未使用的属性?

public class A {
   private int unused1 = 100;// never called anywhere inside object
   public int unused2 = 999;// never called anywhere in the application 
}

我知道我需要好好学习JVM、Compiler和优化。但是需要答案,因为在短时间内我必须决定是从大型代码库(大约 10,000 java 文件)中手动删除所有(尽可能多的)未使用的变量,还是仅依赖于 JVM 优化。

期待一些有趣且富有成效的建议。

TL;DR:不,JVM 编译器 (javac) 不会优化掉未使用的变量。

让我们看一下 javac 编译器生成的字节码。

将其用作测试class:

public class Test {

    private int test = 5;
    private int test2 = 10;
    private String aString = "HelloWorld";
}

产生:

Classfile /C:/Users/Huw/Desktop/Test.class


Last modified 19-Apr-2016; size 331 bytes
  MD5 checksum 1c49b13d1d5d8a2c52924b20753122af
  Compiled from "Test.java"
public class Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#19         // java/lang/Object."<init>":()V
   #2 = Fieldref           #6.#20         // Test.test:I
   #3 = Fieldref           #6.#21         // Test.test2:I
   #4 = String             #22            // HelloWorld
   #5 = Fieldref           #6.#23         // Test.aString:Ljava/lang/String;
   #6 = Class              #24            // Test
   #7 = Class              #25            // java/lang/Object
   #8 = Utf8               test
   #9 = Utf8               I
  #10 = Utf8               test2
  #11 = Utf8               aString
  #12 = Utf8               Ljava/lang/String;
  #13 = Utf8               <init>
  #14 = Utf8               ()V
  #15 = Utf8               Code
  #16 = Utf8               LineNumberTable
  #17 = Utf8               SourceFile
  #18 = Utf8               Test.java
  #19 = NameAndType        #13:#14        // "<init>":()V
  #20 = NameAndType        #8:#9          // test:I
  #21 = NameAndType        #10:#9         // test2:I
  #22 = Utf8               HelloWorld
  #23 = NameAndType        #11:#12        // aString:Ljava/lang/String;
  #24 = Utf8               Test
  #25 = Utf8               java/lang/Object
{
  public Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_5
         6: putfield      #2                  // Field test:I
         9: aload_0
        10: bipush        10
        12: putfield      #3                  // Field test2:I
        15: aload_0
        16: ldc           #4                  // String HelloWorld
        18: putfield      #5                  // Field aString:Ljava/lang/String;
        21: return
      LineNumberTable:
        line 1: 0
        line 3: 4
        line 4: 9
        line 5: 15
}
SourceFile: "Test.java"

如您所见,编译器仍然分配字段属性。

所以不,未使用的变量仍将分配指针(对于对象)和内存(对于基元)。

不幸的是,他们没有这种魔力,希望您别无选择,只能使用 PMD or FindBugs 等工具清理代码,这将帮助您检测此类问题以及更多问题。

我使用 OpenJDK 中全新的 Java 对象布局工具 (http://openjdk.java.net/projects/code-tools/jol/) 进行了一些测试。
测试表明,至少在我的 2 台机器上,JVM、月相等。JIT 无法确定某些字段未使用并且不会对其进行优化。
但即使 JIT 可以,它实际上 进行特定优化也没有必要。
这是测试:

public static long devNull;

public static void main(String[] args) {
    out.println(VM.current().details());

    Wasty wasty = new Wasty();
    Clean clean = new Clean();

    PrintWriter pw = new PrintWriter(out);
    for (int i = 0; i < 10_000_000; i++) {
        devNull += wasty.doSomething();
        devNull += clean.doSomething();
        if (i == 0 || i == 9_999_999) {
            pw.println(GraphLayout.parseInstance(wasty).toFootprint());
            pw.println(GraphLayout.parseInstance(clean).toFootprint());
        }
    }
    pw.close();
}
public class Wasty {
    private long _long;
    private double _double;
    private String _string;
    private long used;
    private int _int;
    private boolean _boolean;

    public long doSomething() {
        return used++;
    }
}
public class Clean {
    private long used;

    public long doSomething() {
        return used++;
    }
}

结果是:

    # WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
    # Running 64-bit HotSpot VM.
    # Using compressed oop with 3-bit shift.
    # Using compressed klass with 3-bit shift.
    # WARNING | Compressed references base/shifts are guessed by the experiment!
    # WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
    # WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
    # Objects are 8 bytes aligned.
    # Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
    # Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

    layout.Wasty@5f205aad footprint:
    COUNT       AVG       SUM   DESCRIPTION
    1        48        48   layout.Wasty
    1                  48   (total)


    layout.Clean@2f410acfd footprint:
    COUNT       AVG       SUM   DESCRIPTION
    1        24        24   layout.Clean
    1                  24   (total)


    layout.Wasty@5f205aad footprint:
    COUNT       AVG       SUM   DESCRIPTION
    1        48        48   layout.Wasty
    1                  48   (total)


    layout.Clean@2f410acfd footprint:
    COUNT       AVG       SUM   DESCRIPTION
    1        24        24   layout.Clean
    1                  24   (total)

因此,就 占用空间(内存性能)而言,重要 未使用的字段 因为它们实际上会消耗内存并且可能会破坏对象的布局。针对未使用字段的第二点是 Joop Eggen 已经提到的代码味道。