【问题标题】:Java - Check if reference to object in Map existsJava - 检查对 Map 中对象的引用是否存在
【发布时间】:2016-07-10 14:29:29
【问题描述】:

几周前,我编写了一个具有以下行为的 Java 类:

  • 每个对象都包含一个最终整数字段
  • 该类包含一个静态 Map(键:整数,内容:MyClass)
  • 每当实例化类的对象时,如果静态映射中已经存在具有所需整数字段的对象,则进行查找:返回它,否则创建一个并将其放入映射中。

作为代码:

public class MyClass
{
    private static Map<Integer, MyClass> map;
    private final int field;

    static
    {
        map = new HashMap<>();
    }

    private MyClass(int field)
    {
        this.field = field;
    }

    public static MyClass get(int field)
    {
        synchronized (map)
        {
            return map.computeIfAbsent(field, MyClass::new);
        }
    }
}

这样我可以确定,每个整数(作为字段)只存在一个对象。我目前担心,这将阻止 GC 收集我不再需要的对象,因为对象始终存储在地图中(存在引用)...

如果我写了一个像这样的循环函数:

public void myFunction() {
    for (int i = 0; i < Integer.MAX_VALUE; i++) {
       MyClass c = MyClass.get(i);
       // DO STUFF
    } 
}

调用该方法后,我最终会在内存中得到Integer.MAX_VALUE 对象。有没有办法可以检查对地图中对象的引用是否存在,否则将其删除?

【问题讨论】:

  • 通常这种技术用于拥有某种缓存以避免在对象已经创建时创建它们。这不是你的目标吗?因为在这种情况下,它们一直分配到程序结束是有意义的。
  • 您可能想要使用软引用或弱引用。例如。请参阅What is the difference between a soft reference and a weak reference in Java? 或者,使用 3rd-party 缓存库,例如见Looking for simple Java in-memory cache
  • @LorisSecuro 是的,这就是我的目标。但我更愿意缓存可能最多说 1000 个对象,然后只缓存当前需要/引用的对象。
  • @Andreas 谢谢,我去看看:)
  • @John 也许你应该试试番石榴缓存

标签: java dictionary reference garbage-collection


【解决方案1】:

这看起来像是the multiton pattern 的典型案例:对于给定的键,您最多希望有一个MyClass 实例。但是,您似乎还想限制创建的实例数量。这很容易通过lazily instantiating 你的MyClass 实例来完成,因为你需要它们。此外,您还想清理未使用的实例:

有没有一种方法可以检查对地图中对象的引用是否存在,否则将其删除?

这正是 JVM 的 garbage collector 的用途;当 Java 核心库已经提供了tools for marking certain references as "not strong" 时,没有理由尝试实现自己的“垃圾收集”形式,即只有在存在强引用时才应引用给定对象(即在 Java 中,“正常”参考)某处引用它。

使用Reference 对象实现

您应该使用Map&lt;Integer, WeakReference&lt;MyClass&gt;&gt;Map&lt;Integer, SoftReference&lt;MyClass&gt;&gt; 而不是Map&lt;Integer, MyClass&gt;WeakReferenceSoftReference 都允许它们引用的MyClass 实例被垃圾收集,如果没有强(阅读:“正常”)对对象的引用。两者的区别在于,前者在所有强引用消失后在下一次垃圾回收动作中释放引用,而后者仅在“必须”时释放引用,即在某个对 JVM 方便的时候(见related SO question)。

另外,不需要同步整个Map:您可以简单地使用ConcurrentHashMap(实现ConcurrentMap)、which handles multi-threading in a way much better than by locking all access to the entire map。因此,您的 MyClass.get(int) 可能如下所示:

private static final ConcurrentMap<Integer, Reference<MyClass>> INSTANCES = new ConcurrentHashMap<>();

public static MyClass get(final int field) {
    // ConcurrentHashMap.compute(...) is atomic <https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html#compute-K-java.util.function.BiFunction->
    final Reference<MyClass> ref = INSTANCES.compute(field, (key, oldValue) -> {
        final Reference<MyClass> newValue;
        if (oldValue == null) {
            // No instance has yet been created; Create one
            newValue = new SoftReference<>(new MyClass(key));
        } else if (oldValue.get() == null) {
            // The old instance has already been deleted; Replace it with a
            // new reference to a new instance
            newValue = new SoftReference<>(new MyClass(key));
        } else {
            // The existing instance has not yet been deleted; Re-use it
            newValue = oldValue;
        }
        return newValue;
    });
    return ref.get();
}

最后,在上面的评论中,你提到你会"prefer to cache maybe up to say 1000 objects and after that only cache, what is currently required/referenced"。虽然我个人认为它没有什么(好的)原因,但可以通过在创建时将它们添加到 INSTANCES 映射来对“第一个” 1000 个对象执行 eager instantiation

private static final ConcurrentMap<Integer, Reference<MyClass>> INSTANCES = createInstanceMap();

private static ConcurrentMap<Integer, Reference<MyClass>> createInstanceMap() {
    // The set of keys to eagerly initialize instances for
    final Stream<Integer> keys = IntStream.range(0, 1000).boxed();
    final Collector<Integer, ?, ConcurrentMap<Integer, Reference<MyClass>>> mapFactory = Collectors
            .toConcurrentMap(Function.identity(), key -> new SoftReference<>(new MyClass(key)));
    return keys.collect(mapFactory);
}

如何定义哪些对象是“第一个”对象取决于您;在这里,我只是使用整数键的自然顺序,因为它适用于一个简单的示例。

【讨论】:

  • 谢谢,这真的很酷!但是由于键是Integer-objects,它们是否也需要是SoftReference
  • SoftReference 不能很好地用作(Hash)Map 键,因为它没有实现hash()equals()。此外,Integer objects may be cached by the JVM 等相同的对象无论如何都可以在您的应用程序中的所有位置使用,这意味着由于在地图以外的任何其他地方没有引用该键而被自动清除的可能性非常小。 .
  • ...不过,如果您想要一个缓存来清除其未在其他地方引用的键,则有一个 WeakHashMap 用于此目的。
  • 我在上面看到了您的 cmets,并添加了一个示例,说明您如何主动初始化“第一个”1000 个对象。
【解决方案2】:

您检查缓存的功能值得畏惧。首先,正如您所说,它创建了所有缓存对象。其次,它会迭代Integer.MAX_VALUE 次。

更好的是:

public void myFunction() {
    for(MyClass c : map.values()) {
       // DO STUFF
    } 
}

手头的问题:是否有可能找出一个对象是否有对它的引用?

是的。有可能的。但你不会喜欢的。

http://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html

jvmtiError
IterateOverReachableObjects(jvmtiEnv* env,
        jvmtiHeapRootCallback heap_root_callback,
        jvmtiStackReferenceCallback stack_ref_callback,
        jvmtiObjectReferenceCallback object_ref_callback,
        void* user_data)

循环遍历堆中所有可达的对象。如果 MyClass 对象是可访问的,那么,它是可访问的。

当然,通过将对象存储在缓存中,您可以使其可访问,因此您必须将缓存更改为 WeakReferences,并查看是否可以从迭代中排除这些对象。

而且您不再使用纯 Java,并且可能并非所有 VM 都支持 jvmti

正如我所说,你不会喜欢它。

【讨论】:

  • 函数(最多迭代到Integer.MAX_VALUE)只是一个例子,当缓存可能失败并且我很清楚如何迭代java.util.map。如果你有兴趣,可以看看你上面的答案。
猜你喜欢
  • 2013-09-26
  • 2012-07-27
  • 2020-06-18
  • 1970-01-01
  • 1970-01-01
  • 2018-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多