我对 java 如何处理未使用的变量有疑问。
假设我有以下代码:
int notUsedVariable = aMethodThatExecutesSomethingImportantAndReturnsInt(someParameter);
然后我从不在代码中使用 notUsedVariable。是否会实际存储该变量,或者java足够聪明以在编译时忽略该变量?
谢谢!
我的观察是 javac 可能会省略未使用变量的存储操作,如果:
final
并在声明中初始化;-g:vars
)进行编译如果您使用 编译-g:vars
,javac 将保持变量加载和存储以用于调试目的。它似乎不认为非最终变量有资格删除。
为自己测试。我使用 JDK 7 的结果如下。结果与 JDK 8 EAP 相同。
输入:
class MyTest {
public static void main(String... args) {
int a = 1;
}
}
输出:
public static void main(java.lang.String... p0);
Flags: PUBLIC, STATIC, VARARGS
Code:
stack=1, locals=2, arguments=1
0: iconst_1
1: istore_1
2: return
输入:
class MyTest {
public static void main(String... args) {
final int a = 1;
}
}
输出:
public static void main(java.lang.String... p0);
Flags: PUBLIC, STATIC, VARARGS
Code:
stack=1, locals=2, arguments=1
0: return
正如其他人所说,在任何一种情况下,我都希望 JIT 优化器省略任何不必要的存储操作。
这取决于。
如果notUsedVariable
是一个局部变量,JIT 编译器可能会忽略该赋值(但我们谈论的是一个寄存器读/写,即现代桌面处理器上的亚纳秒级的东西)。正如 MattBall 所证明的,字节码将保留分配。
如果notUsedVariable
是该类的成员,则需要存储结果,因为稍后可能会访问该字段,并且编译器可能无法证明其他情况(例如,可以加载一个尚不存在的新类) .
如果您只关心静态编译步骤,而不是JIT,这很容易通过比较从两个稍微不同的类生成的字节码来检查,使用javap
:
class WithLocalVar {
private static int methodWithSideEffect() {
System.out.println();
return 42;
}
public static void main(String[] args) {
int result = methodWithSideEffect();
}
}
class WithoutLocalVar {
private static int methodWithSideEffect() {
System.out.println();
return 42;
}
public static void main(String[] args) {
methodWithSideEffect();
}
}
✗ javac With*
✗ javap -c WithLocalVar
Compiled from "WithLocalVar.java"
class WithLocalVar extends java.lang.Object{
WithLocalVar();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #4; //Method methodWithSideEffect:()I
3: istore_1
4: return
}
✗ javap -c WithoutLocalVar
Compiled from "WithoutLocalVar.java"
class WithoutLocalVar extends java.lang.Object{
WithoutLocalVar();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #4; //Method methodWithSideEffect:()I
3: pop
4: return
}
因此,不,编译器不会优化掉istore_1
. JIT 是另一个故事……
javac 不执行很多优化。另一方面,JIT 确实
查看http://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/
这是一个qoute。
更重要的是,javac 编译器不执行简单的优化,如循环展开、代数简化、强度降低等。为了获得这些好处和其他简单的优化,程序员必须在 Java 源代码上执行它们,而不是依赖 javac 编译器来执行它们。
还有另一个线程对此进行了更详细的介绍。 Java编译器优化
让我们编译一个例子
public class Test {
public static void main(String... args) {
int a = 1;
}
}
我们得到
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String...);
Code:
0: iconst_1 // push integer 1 on stack
1: istore_1 // pop integer from stack, store it in local variable 1
2: return
}
我们可以看到局部变量没有被删除。它被存储了。
请记住,在执行时可能会发生优化。
它将基于变量的范围进行存储,当范围结束时,垃圾收集将清理变量使用的内存。
我为类变量和局部变量编辑了一次我的测试类,然后使用 Eclipse 检查类文件。(Eclipse 抱怨从未使用过该变量。)
// Compiled from UserLoadTest.java (version 1.6 : 50.0, super bit)
public class org.dev.user.UserLoadTest extends org.test.BaseTestCase {
// Field descriptor #6 I
public int myVariable;
...我们可以看到类文件看到了这个
@org.junit.Test
public void testBasicUserLoad() throws java.io.IOException, org.custommonkey.xmlunit.exceptions.XpathException;
0 aload_0 [this]
1 ldc <String "user_01.xml"> [24]
...
org.custommonkey.xmlunit.XMLAssert.assertXpathEvaluatesTo(java.lang.String, java.lang.String, org.w3c.dom.Document) : void [85]
223 aload_2 [reloaded]
224 invokevirtual org.testHarness.Result.getDocument() : org.dom4j.dom.DOMDocument [81]
227 astore_3 [d]
228 return
第 224 行是变量的简单声明,使用
Document d = reloaded.getDocument();
这与d
类文件无关,但会识别该变量已创建。