Java StackMapTable是否影响垃圾收集行为?
我有这样的代码:Java StackMapTable是否影响垃圾收集行为?,java,garbage-collection,Java,Garbage Collection,我有这样的代码: public class TestGC { private static final int _10MB = 10 * 1024 * 1024; // 10MB public static void main(String[] args) { test1(); // test2(); } public static void test1() { int i = 1; if (i
public class TestGC {
private static final int _10MB = 10 * 1024 * 1024; // 10MB
public static void main(String[] args) {
test1();
// test2();
}
public static void test1() {
int i = 1;
if (i > 0) {
byte[] data = new byte[_10MB];
}
System.gc();
}
public static void test2() {
if (true) {
byte[] data = new byte[_10MB];
}
System.gc();
}
}
我使用jvm选项运行它-verbose:gc
,我的java环境:
java版本“1.7.0_79”
Java(TM)SE运行时环境(build 1.7.0_79-b15)
Java HotSpot(TM)64位服务器虚拟机(构建24.79-b02,混合模式)
案例1:
使用调用的方法test1()
运行,控制台输出:
[GC 13312K->616K(116736K), 0.0014246 secs]
[Full GC 616K->554K(116736K), 0.0125266 secs]
[GC 13312K->10936K(116736K), 0.0092033 secs]
[Full GC 10936K->10788K(116736K), 0.0155626 secs]
数据
var由JVM收集
案例2:
使用调用的方法test2()
运行,控制台输出:
[GC 13312K->616K(116736K), 0.0014246 secs]
[Full GC 616K->554K(116736K), 0.0125266 secs]
[GC 13312K->10936K(116736K), 0.0092033 secs]
[Full GC 10936K->10788K(116736K), 0.0155626 secs]
数据
未收集var
我通过命令javap
为方法生成字节码:
test1()
test2()
我的猜测是:当方法test1()
执行堆栈映射帧时,局部变量被重置,导致插槽1(data
located)被清除
有人可以详细解释一下吗?局部变量的范围是编译时的事情。对于字节码,它只影响最近写入局部变量索引的值。对于垃圾收集器来说,它只影响后续可以访问哪个值 但是,检测随后未使用的值可能取决于代码的编译/优化级别。在您的简单测试中,代码将始终以解释方式运行,因此JVM并不总是检测到创建的数组实际上未使用。当您使用
-Xcomp
运行测试时,它总是会立即被收集
您发现的行为取决于在字节码中找到的条件分支,但不取决于堆栈映射的存在,您可以通过使用-target 1.5
(也需要-source 1.5
)编译来轻松验证堆栈映射,这样编译的类文件中就不存在堆栈映射,而是在相同的运行时环境中运行;行为没有改变
请注意,您的
if (true) {
byte[] data = new byte[_10MB];
}
System.gc();
这与
{
byte[] data = new byte[_10MB];
}
System.gc();
当您在编译时常量上进行分支时。但是,由于您没有覆盖该值,例如在作用域结束后创建并使用另一个变量,因此字节码与
byte[] data = new byte[_10MB];
System.gc();
所有这些变体都表现出不收集仍然由堆栈框架引用的数组的相同行为,除非代码已编译
相比之下
int i = 1;
if (i > 0) {
byte[] data = new byte[_10MB];
}
System.gc();
带有条件分支,因此在System.gc()
点,无法使用数组引用,因为可能通过未初始化此变量的路径到达代码点
同样,数组也是使用
for(boolean b=true; b; b=!b) {
byte[] data = new byte[_10MB];
}
System.gc();
因为条件分支可能会绕过变量初始化,而
do {
byte[] data = new byte[_10MB];
} while(false);
System.gc();
由于变量始终处于初始化状态,因此未收集数组
还有
public static void test1() {
int i = 1;
if (i > 0) {
byte[] data = new byte[_10MB];
}
else {
byte[] data = new byte[_10MB];
}
System.gc();
}
数组不会被收集,因为无论代码采用哪个分支,变量总是被初始化的。如前所述,只有在解释执行中
这表明这里没有使用堆栈映射,因为堆栈映射明确声明,分支合并点没有
字节[]
变量,就像原始的test1()
变量一样。-Xcomp
。。。我从来没有真正理解过这个标志,它是否能够在不进行任何内联/转义分析/循环展开或任何其他由C1
或C2
JIT编译器进行的潜在优化的情况下编译机器代码?另一方面,我在很久以前就绝望地放弃了理解堆栈映射的想法:(test2()
和这个变体,我想我已经知道了,因为它们在字节码上是等价的。但是我仍然混淆了test1()
和变体,我想可以给出答案,但我还没有理解。@Eugene-Xcomp
是一个没有分层编译的时代的老选项。它只是强制编译,或者,为了让今天的术语更容易理解,我们可以说它禁止解释执行。它与-Xint 这会强制解释执行。我认为-Xcomp
根本不会影响C1
和C2
。堆栈映射功能并不难理解,它只是向验证器提供提示,但不会影响任何其他内容。如本答案所述,这些提示可以帮助JVM理解(快速)哪些变量未使用,但尚未使用。@Holger谢谢你,突然间-Xcomp
很有意义,关于这个主题的最热门问题实际上比这个评论更糟糕。。。