【问题标题】:Is WeakHashMap ever-growing, or does it clear out the garbage keys?WeakHashMap 是在不断增长,还是清除了垃圾键?
【发布时间】:2019-03-18 07:02:41
【问题描述】:

我正在尝试将WeakHashMap 用作弱引用的并发Set

    this.subscribers =
            Collections.synchronizedSet(
                    Collections.newSetFromMap(
                            new WeakHashMap <>()
                    )
            );

当一个元素进入垃圾收集时,我的集合继续将它作为集合的一部分进行报告。因此,地图似乎在不断增长。

文档说:

当一个键被丢弃时,它的条目实际上从映射中删除,...

但实际情况似乎并非如此。

WeakHashMap 是否有清除碎屑的时候?

【问题讨论】:

  • “当一个元素进入垃圾回收站时,我的集合继续将它作为集合的一部分进行报告” - 你如何确定这一点?
  • @user2357112 我打电话给Set::size()
  • “这个结果是一个快照,可能不会反映未处理的条目,因为它们不再被引用,所以在下次尝试访问之前将被删除。”
  • 另外,您如何确定这些对象已被垃圾回收?
  • @user2357112 事实上,我一定是错误地假设对象是垃圾收集的。请参阅我发布的答案,其中包含一个小实验,表明 Set 报告它的大小下降是由于调用 Set::remove 还是由于成为垃圾。我将不得不更彻底地探索我的原始代码问题。

标签: java garbage-collection weakhashmap


【解决方案1】:

是的,在真正收集垃圾之后清除了键

是的,WeakHashMap 确实清除了碎屑。 已进入垃圾收集的键不再以大小报告。但是您必须等待垃圾收集实际发生。

您似乎对将要进行垃圾收集的对象不正确。也许您的对象已成为垃圾收集的候选,但尚未被收集。尝试调用垃圾收集器并等待它完成。但请记住,对System.gc() 的调用只是对 JVM 的建议,根据您的 JVM 实现和当前运行时场景,可能会被忽略。

这是一个完整的示例应用程序。请注意,无论是调用 Set::remove 还是让对象超出范围,Set 都会报告 size 的减少。

package com.basilbourque.example;

import java.util.Collections;
import java.util.Set;
import java.util.UUID;
import java.util.WeakHashMap;

public class WeakHashMapExercise {

    public static void main ( String[] args ) {
        WeakHashMapExercise app = new WeakHashMapExercise();
        app.doIt();
    }

    private void doIt ( ) {
        Set < UUID > set =
                Collections.synchronizedSet(
                        Collections.newSetFromMap(
                                new WeakHashMap <>()
                        )
                );

        UUID uuid1 = UUID.fromString( "a8ee1e34-cead-11e8-a8d5-f2801f1b9fd1" );
        UUID uuid2 = UUID.fromString( "39bda2b4-5885-4f56-a900-411a49beebac" );
        UUID uuid3 = UUID.fromString( "0b630385-0452-4b96-9238-20cdce37cf55" );
        UUID uuid4 = UUID.fromString( "98d2bacf-3f7f-4ea0-9c17-c91f6702322c" );

        System.out.println( "Size before adding: " + set.size() );

        set.add( uuid1 );
        set.add( uuid2 );
        set.add( uuid3 );
        set.add( uuid4 );

        System.out.println( "Size after adding 4 items: " + set.size() );  // Expect 4.

        set.remove( uuid3 );

        System.out.println( "Size after removing item # 3: " + set.size() );  // Expect 3.

        uuid2 = null;  // Release that UUID to garbage-collection.

        // That released object may still appear in our `Set` until garbage collection actually executes. 
        System.gc(); // Ask the JVM to run the garbage-collection. Only a suggestion, may be ignored.
        try {
            Thread.sleep( 1_000 );  // Wait a moment, just for the heck of it.
        } catch ( InterruptedException e ) {
            e.printStackTrace();
        }

        System.out.println( "Size after making garbage of item # 2: " + set.size() );  // Expect 2.

        for ( UUID uuid : set ) {
            System.out.println( uuid.toString() );
        }


    }
}

看到这个code run live at IdeOne.com

添加前的大小:0

添加4项后的尺寸:4

删除第 3 项后的大小:3

item#2制作垃圾后的尺寸:2

就我而言,使用Java 10.0.2OpenJDK 版本的Zulu JVM 来自Azul Systems,垃圾收集器似乎确实应我的请求激活。如果我将延迟注释掉一秒钟,或者System.gc 调用,那么最后报告的大小仍然是3,而不是预期的2

你甚至可以在running this code live at IdeOne.com 时看到这种行为。注意下面最后一项是3,而上面是2

添加前的大小:0

添加4项后的尺寸:4

删除第 3 项后的大小:3

item#2制作垃圾后的尺寸:3

【讨论】:

  • 是的,在实际收集垃圾后清除了密钥 - 并不完全正确。当 GC 清除弱引用时,它会向引用队列发布一个“事件”——该过程是异步的,即使 GC 已经“清除”了您的键,WeakHashMap 仍然对该值具有强引用。实际清理发生在 1) GC 已将事件发布到引用队列(您无法控制 when 这种情况发生)2) 您调用任何 other 方法WeakHashMap - 将进行所需的清理。
  • @Eugene 感谢您提供详细信息。您能否引用此信息的来源以供进一步阅读?
  • 我已经发布了一个包含一些细节和 cmets 的答案,应该很明显 IMO 发生了什么......
【解决方案2】:

garbage-collection 清除weak reference 时,它会将“事件”发布到引用队列。该过程是asynchronous,即使 GC 已经“清除”了您的密钥,WeakHashMap 仍然对该值有很强的引用。实际清理发生在以下情况:

  1. 垃圾收集器已将事件发布到参考队列(您无法控制何时发生)。
  2. 您可以在 WeakHashMap 上调用任何其他方法 - 这将进行所需的清理。

这里有一个例子来说明发生了什么。

public class WeakHashMapInAction {

    public static void main(String[] args) {

        Key key = new Key();
        KeyMetadata keyMeta = new KeyMetadata("keyMeta");

        WeakHashMap<Key, KeyMetadata> map = new WeakHashMap<>();
        map.put(key, keyMeta);

        // wrap the key into a weakReference
        WeakReference<Key> keyReference = new WeakReference<>(key);

        // force key to be GC-ed
        key = null;
        for (; keyReference.get() != null; ) {
            System.gc();
        }

        // at this point keyReference::get returns null,
        // meaning the GC has reclaimed "key";
        // that does NOT mean WeakHashMap removed that entry though

        // you can enable this code to see that "not yet collected" is not printed at all
        // since you are giving enough time for the Reference thread to post to that ReferenceQueue
        // LockSupport.parkNanos(10000000);

        while (map.size() == 1) {
            // if you run this enough times, you will see this sometimes is printed
            // even if WeakHashMap::size calls "expungeStaleEntries" internally
            // it does not mean that the event to the queue was pushed in time
            // by the Reference thread
            System.out.println("not yet collected");
        }

        System.out.println("collected");

    }


    static class Key {

    }

    @RequiredArgsConstructor
    @Getter
    static class KeyMetadata {
        private final String someInfo;

        // Constructor.
        KeyMetadata ( String someInfo ) { this.someInfo = someInfo; }
    }

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-21
    • 2011-01-28
    • 2012-04-09
    • 1970-01-01
    相关资源
    最近更新 更多