2

I thought WeakReference refer object will be finalized after System.gc() called, but I'm wrong.

Here are two test cases, the only difference is WeakReference constructor, the first one new an object while the second one use a referer, and they have different performance, I don't konw why...

Weak reference objects, which do not prevent their referents from being made finalizable, finalized, and then reclaimed. Weak references are most often used to implement canonicalizing mappings.

Suppose that the garbage collector determines at a certain point in time that an object is weakly reachable. At that time it will atomically clear all weak references to that object and all weak references to any other weakly-reachable objects from which that object is reachable through a chain of strong and soft references. At the same time it will declare all of the formerly weakly-reachable objects to be finalizable. At the same time or at some later time it will enqueue those newly-cleared weak references that are registered with reference queues.

package com.zeng.javaReference;

import org.junit.Test;

import java.lang.ref.WeakReference;

/**
 * @author zeng
 * @version 1.0.0
 * @date 2020-05-11
 */
public class WeakReferenceTest {

    @Test
    public void weakRefRemoved() {
        WeakReference<Apple> weakReference = new WeakReference<>(new Apple("green-apple"));

        System.gc();

        if (weakReference.get() == null) {
            System.out.println("GC remove weakReference!");
        } else {
            System.out.println("weakReference still alive");
        }
    }

    @Test
    public void weakRefNotRemoved() {
        Apple apple = new Apple("green-apple");
        WeakReference<Apple> weakReference = new WeakReference<>(apple);

        System.gc();

        if (weakReference.get() == null) {
            System.out.println("GC remove weakReference!");
        } else {
            System.out.println("weakReference still alive");
        }
    }

    public static class Apple {

        private String name;

        public Apple(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("Apple: " + name + " finalized。");
        }

        @Override
        public String toString() {
            return "Apple{" +
                    "name='" + name + '\'' +
                    '}' + ", hashCode:" + this.hashCode();
        }
    }

}

Holger
  • 285,553
  • 42
  • 434
  • 765
anon_zeng
  • 41
  • 4
  • Hello, please provide details of which differences you've found in your test. Also, it seems you need to review [the answer on testing weak references](https://stackoverflow.com/questions/11174328/testing-weakreference) and update your tests accordingly. – Nowhere Man May 11 '20 at 09:12
  • @AlexRudenko I don't think he does, his examples make perfect sense, see [this](https://stackoverflow.com/a/62094119/1059372) – Eugene May 29 '20 at 20:50
  • 1
    A `WeakReference` does not prevent the referent’s garbage collection, but other references like in the `apple` variable do. What did you think, that a single weak reference may allow an object’s collection, regardless of how much it is used in other code? Further, don’t confuse garbage collection with finalization. – Holger Jun 30 '20 at 08:58

2 Answers2

0

System.gc() may or may not trigger garbage collection.

By manually calling it your are just giving hints to the JVM, but weather it will honor it is entirely up to the java runtime. The JVM is very sophisticated system and doesn't allow direct access to the resources.

As for WeakReference from the docs

A weak reference is a reference made that is not strong enough to make the object to remain in memory. So, weak references can let the garbage collector decide an object’s reachability and whether the object in question should be kept in memory or not.

Again the decision weather to collect the object is entirely up to the collector.

Borislav Stoilov
  • 3,247
  • 2
  • 21
  • 46
  • 1
    But both these two test cases call System.gc(), why the first one output "GC remove weakReference!", but the second one output "weakReference still alive" – anon_zeng May 12 '20 at 07:14
  • _exactly_. OP has a perfect point here. also read [this](https://stackoverflow.com/a/62094119/1059372) – Eugene May 29 '20 at 20:49
0

This is rather interesting indeed.

Assuming you use a GC collector that actually does something when calling System.gc(), like G1 let's say and you did not disable explicit invocation to GC via -XX:+DisableExplicitGC, there is an explanation to this, somehow.

The weakRefNotRemoved in theory should work the same as weakRefRemoved. This has to do with scope and reachability, more details about this here.

The point is that in weakRefNotRemoved, apple reference after the invocation System.gc() is not used by anyone, so at that point in time it is un-reacheable. So, in theory, a GC can reclaim that, but for whatever reason it will not. I can only assume that this special path is only triggered when there is real memory pressure.

To prove a point, we can change that code slightly, to:

 public static void weakRefNotRemoved() {
    Apple apple = new Apple("green-apple");
    WeakReference<Apple> weakReference = new WeakReference<>(apple);

    // explicitly set to null
    apple = null;
    System.gc();

    if (weakReference.get() == null) {
        System.out.println("GC remove weakReference!");
    } else {
        System.out.println("weakReference still alive");
    }
}

And now, GC will indeed clean it. In general, if you try to prove a point, it would be better to call that under a while loop because there are no guarantees about the cycle that will "catch" that reference. Also because the clearing happens in an asynchronous fashion. Something like:

 public static void weakRefNotRemoved() {
    Apple apple = new Apple("green-apple");
    WeakReference<Apple> weakReference = new WeakReference<>(apple);

    apple = null;
    while (weakReference.get() != null) {
        System.out.println("weakReference still alive");
        System.gc();
        LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
    }

    System.out.println("GC remove weakReference!");

}

And it's perfectly legal to get such an output, for example:

weakReference still alive  // zero or more of these messages
GC remove weakReference!
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 2
    It’s mainly dependent on the optimization state of the method and since the original method is executed only a single time and doesn’t contain long running loops, it’s running interpreted without any usage analysis. – Holger Jun 30 '20 at 09:59