这失败了
public void testWeak() throws Exception {
waitGC();
{
Sequence a = Sequence.valueOf("123456789");
assert Sequence.used() == 1;
a.toString();
}
waitGC();
}
private void waitGC() throws InterruptedException {
Runtime.getRuntime().gc();
short count = 0;
while (count < 100 && Sequence.used() > 0) {
Thread.sleep(10);
count++;
}
assert Sequence.used() == 0: "Not removed!";
}
测试失败。告诉 未删除!
。
这有效:
public void testAWeak() throws Exception {
waitGC();
extracted();
waitGC();
}
private void extracted() throws ChecksumException {
Sequence a = Sequence.valueOf("123456789");
assert Sequence.used() == 1;
a.toString();
}
private void waitGC() throws InterruptedException {
Runtime.getRuntime().gc();
short count = 0;
while (count < 100 && Sequence.used() > 0) {
Thread.sleep(10);
count++;
}
assert Sequence.used() == 0: "Not removed!";
}
似乎大括号并没有影响弱点。
一些官方资源?
最佳答案
作用域是编译时的东西。它不决定对象在运行时的可达性,仅对实现细节有间接影响。
考虑以下测试变体:
static boolean WARMUP;
public void testWeak1() throws Exception {
variant1();
WARMUP = true;
for(int i=0; i<10000; i++) variant1();
WARMUP = false;
variant1();
}
private void variant1() throws Exception {
AtomicBoolean track = new AtomicBoolean();
{
Trackable a = new Trackable(track);
a.toString();
}
if(!WARMUP) System.out.println("variant1: "
+(waitGC(track)? "collected": "not collected"));
}
public void testWeak2() throws Exception {
variant2();
WARMUP = true;
for(int i=0; i<10000; i++) variant2();
WARMUP = false;
variant2();
}
private void variant2() throws Exception {
AtomicBoolean track = new AtomicBoolean();
{
Trackable a = new Trackable(track);
a.toString();
if(!WARMUP) System.out.println("variant2: "
+(waitGC(track)? "collected": "not collected"));
}
}
static class Trackable {
final AtomicBoolean backRef;
public Trackable(AtomicBoolean backRef) {
this.backRef = backRef;
}
@Override
protected void finalize() throws Throwable {
backRef.set(true);
}
}
private boolean waitGC(AtomicBoolean b) throws InterruptedException {
for(int count = 0; count < 10 && !b.get(); count++) {
Runtime.getRuntime().gc();
Thread.sleep(1);
}
return b.get();
}
在我的机器上,它打印:
variant1: not collected
variant1: collected
variant2: not collected
variant2: collected
如果无法复现,可能需要提高预热迭代次数。
它演示了什么:a
是否在范围内(变体 2)或不在范围内(变体 1)无关紧要,在任何一种情况下,对象都没有在冷执行中被收集,但是得到了在多次预热迭代后收集,换句话说,在优化器启动后收集。
形式上,a
在我们调用 waitGC()
时总是有资格进行垃圾回收,因为它未被使用观点。这是 how reachability is defined :
A reachable object is any object that can be accessed in any potential continuing computation from any live thread.
在此示例中,潜在的连续计算无法访问该对象,因为不存在可访问该对象的此类后续计算。但是,无法保证特定 JVM 的垃圾收集器每次始终能够识别所有这些对象。事实上,即使 JVM 根本没有垃圾收集器也仍然符合规范,尽管可能不是本意。
代码优化对可达性分析产生影响的可能性也已明确 mentioned in the specification :
Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable. For example, a Java compiler or code generator may choose to set a variable or parameter that will no longer be used to
null
to cause the storage for such an object to be potentially reclaimable sooner.
那么技术上会发生什么?
如前所述,范围是编译时的事情。在字节码级别,离开花括号定义的范围没有任何效果。变量 a
超出范围,但它在堆栈帧中的存储仍然存在并保存引用,直到被另一个变量覆盖或直到方法完成。编译器可以自由地为另一个变量重用存储,但在这个例子中,不存在这样的变量。因此,上面示例的两个变体实际上生成了相同的字节码。
在未优化的执行中,堆栈帧中仍然存在的引用被视为阻止对象收集的引用。在优化执行中,引用仅保留到最后一次实际使用为止。内联它的字段可以允许它的收集甚至更早,直到它在构造后立即被收集(或者根本没有被构造,如果它没有 finalize()
方法)。最末端是finalize() called on strongly reachable object in Java 8 ……
当您插入另一个变量时,情况会发生变化,例如
private void variant1() throws Exception {
AtomicBoolean track = new AtomicBoolean();
{
Trackable a = new Trackable(track);
a.toString();
}
String message = "variant1: ";
if(!WARMUP) System.out.println(message
+(waitGC(track)? "collected": "not collected"));
}
然后,a
的存储在 a
的范围结束后被 message
重用(这当然是特定于编译器的)并且即使在未优化的执行中,对象也会被收集。
请注意,关键方面是存储的实际覆盖。如果你使用
private void variant1() throws Exception {
AtomicBoolean track = new AtomicBoolean();
{
Trackable a = new Trackable(track);
a.toString();
}
if(!WARMUP)
{
String message = "variant1: "
+(waitGC(track)? "collected": "not collected");
System.out.println(message);
}
}
message
变量使用与 a
相同的存储空间,但它的赋值只发生在 调用 waitGC(track) 之后
,因此您将获得与原始变体相同的未优化执行行为。
顺便说一句,不要对局部循环变量使用short
。 Java 始终使用 int
进行 byte
、short
、char
和 int
计算(如您所知,例如,当尝试编写 shortVariable = shortVariable + 1;
时,并要求它将结果值剪切为 short
(当您使用 shortVariable++
), 添加了一个附加操作,所以如果您认为使用short
可以提高效率,请注意它实际上是相反的。
关于java - WeakReference 没有收集在大括号中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47326757/