question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Local is not cleared, causing an object not to be GC'd

See original GitHub issue

Compiler 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:closed
  • Created 2 years ago
  • Comments:7 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
lrytzcommented, Jan 3, 2022

Looking at -Xprint:all, the local variable seems to be added here:

[[syntax trees at end of                   erasure]] // Test.scala

        mark = ref.get().asInstanceOf[java.lang.ref.Reference].get()


[[syntax trees at end of MegaPhase{elimErasedValueType, pureStats, vcElideAllocations, arrayApply, elimPolyFunction, tailrec, completeJavaEnums, mixin, lazyVals, memoize, nonLocalReturns, capturedVars}]] // Test.scala


        {
          val ev$1: Object =
            ref.get().asInstanceOf[java.lang.ref.Reference].get()
          mark.elem = ev$1
        }
0reactions
som-snyttcommented, Jan 3, 2022

I did look at uses of SyntheticValDef because of the lack of a SyntheticVarDef. (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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

If not cleared ThreadLocal variable, What will happen?
The javadoc says: after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other ...
Read more >
Realm file size inflates when using gcd to create new objects
Would like to create new objects on a background thread with gcd. Expected Results. Realm file size should not inflate ...
Read more >
NSKeyedArchiver, GCD, Threads, and You | Akshay Easwaran
In College Football Coach for iOS, I rely on the AutoCoding library to get objects in my data model to automagically adopt NSCoding...
Read more >
Swift 5.5: Replacing GCD With Async/Await - BiTE Interactive
A block of code can be called asynchronously by creating a Task object and handing that code to its initializer. That can be...
Read more >
iOS Concurrent Programming. Basic Concepts | by Xiao Jiang
Thread: A context of execution of a program, scheduled for running by the Operating System. Any number of these can exist at once....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found