Local is not cleared, causing an object not to be GC'd
See original GitHub issueCompiler version
3.1.0
Minimized code
import java.lang.ref.WeakReference
import java.util.concurrent.atomic.AtomicReference
final class Mark
final object MyTest {
def main(args: Array[String]): Unit = {
myTest()
}
def myAssert(cond: => Boolean): Unit = {
assert(cond)
}
def myTest(): Unit = {
val ref = new AtomicReference[WeakReference[AnyRef]]
var mark: AnyRef = null
assert(ref.compareAndSet(null, new WeakReference(new Mark)))
mark = ref.get().get()
myAssert(mark ne null) // in theory this could fail, but it isn't
mark = null
while (ref.get().get() ne null) {
System.gc()
Thread.sleep(1L)
}
}
}
Output
The program never exits.
Expectation
I think eventually the GC should collect the Mark
instance, and the while
loop should finish. (Works fine on Scala 2.13.7.)
Looking at a heap dump, the Mark
instance is a GC root.
Looking at the JVM bytecode, it seems there is an “unnamed” local variable in the method myTest
(i.e., it is not in the LocalVariableTable). The LocalVariableTable contains slots 0 (this
), 1 (ref
), and 2 (mark
). In the bytecode there is a store to slot 3:
49: invokevirtual #78 // Method java/lang/ref/Reference.get:()Ljava/lang/Object;
52: astore_3
53: aload_2
54: aload_3
55: putfield #82 // Field scala/runtime/ObjectRef.elem:Ljava/lang/Object;
I think 49: invokevirtual
is the second .get()
in the line mark = ref.get().get()
. Then 52: astore_3
is the store to this “unnamed local 3”. Then 55: putfield
stores it in the ObjectRef
corresponding to the var mark
.
However, when later we (try to) clear the reference in the line mark = null
, only the ObjectRef
is cleared, the “unnamed local 3” isn’t:
76: aconst_null
77: putfield #82 // Field scala/runtime/ObjectRef.elem:Ljava/lang/Object;
Interestingly, right before this, there is a store of null to another unnamed local, in slot 4 (I couldn’t figure out, what is this local 4):
69: aconst_null
70: astore 4
72: aload_2
73: aload 4
75: pop
Another observation: the myAssert
seems necessary to reproduce the issue (with a normal assert
, it works fine).
Issue Analytics
- State:
- Created 2 years ago
- Comments:7 (2 by maintainers)
Top GitHub Comments
Looking at
-Xprint:all
, the local variable seems to be added here:I did look at uses of
SyntheticValDef
because of the lack of aSyntheticVarDef
. (I learned that you can assign to a val unless they said-Ycheck:all
.) By good first issue, I mean I know how to assign to a variable, but trying to do it in this context requires learning a lot of context.