Java 如果达到内存大小限制,如何从映射中删除元素?

Java 如果达到内存大小限制,如何从映射中删除元素?,java,caching,memory-management,guava,lru,Java,Caching,Memory Management,Guava,Lru,我使用ConcurrentLinkedHashMap实现了一个LRU缓存。在同一个映射中,如果我的映射达到如下所示的特定限制,我将清除事件 我有一个MAX_SIZE变量,它相当于3.7 GB,一旦我的映射达到该限制,我就从映射中清除事件 下面是我的代码: import java.util.concurrent.ConcurrentMap; import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; import c

我使用
ConcurrentLinkedHashMap
实现了一个LRU缓存。在同一个映射中,如果我的映射达到如下所示的特定限制,我将清除事件

我有一个
MAX_SIZE
变量,它相当于3.7 GB,一旦我的映射达到该限制,我就从映射中清除事件

下面是我的代码:

import java.util.concurrent.ConcurrentMap;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import com.googlecode.concurrentlinkedhashmap.EvictionListener;

// does this really equal to 3.7 GB? can anyone explain this?
public static final int MAX_SIZE = 20000000; //equates to ~3.7GB with assumption that each event is 200 bytes AVG

public static EvictionListener<String, DataObject> listener = new EvictionListener<String, DataObject>() {
    public void onEviction(String key, DataObject value) {
        deleteEvents();
    }
};
public static final ConcurrentMap<String, DataObject> holder = new ConcurrentLinkedHashMap.Builder<String, DataObject>()
            .maximumWeightedCapacity(MAX_SIZE).listener(listener).build();

private static void deleteEvents() {
    int capacity = MAX_SIZE - (MAX_SIZE * (20 / 100));
    if (holder.size() >= capacity) {
        int numEventsToEvict = (MAX_SIZE * 20) / 100;
        int counter = 0;
        Iterator<String> iter = holder.keySet().iterator();
        while (iter.hasNext() && counter < numEventsToEvict) {
            String address = iter.next();
            holder.remove(address);
            System.out.println("Purging Elements: " +address);
            counter++;
        }
    }
}

// this method is called every 30 seconds from a single background thread 
// to send data to our queue
public void submit() {
    if (holder.isEmpty()) {
        return;
    }

    // some other code here

    int sizeOfMsg = 0;
    Iterator<String> iter = holder.keySet().iterator();
    int allowedBytes = MAX_ALLOWED_SIZE - ALLOWED_BUFFER;

    while (iter.hasNext() && sizeOfMsg < allowedBytes) {
        String key = iter.next();
        DataObject temp = holder.get(key);

        // some code here

        holder.remove(key);

        // some code here to send data to queue
    }
}   

// this holder map is used in below method to add the events into it.
// below method is being called from some other place.
public void addToHolderRequest(String key, DataObject stream) {
    holder.put(key, stream);
}
import java.util.concurrent.ConcurrentMap;
导入com.googlecode.concurrentlinkedhashmap.concurrentlinkedhashmap;
导入com.googlecode.concurrentlinkedhashmap.executionListener;
//这真的等于3.7GB吗?有人能解释一下吗?
公共静态最终整数最大值=20000000//假设每个事件平均为200字节,则相当于~3.7GB
public static executionlistener=new executionlistener(){
public void onEviction(字符串键、数据对象值){
deleteEvents();
}
};
公共静态最终ConcurrentMap持有者=新ConcurrentLinkedHashMap.Builder()
.maximumWeightedCapacity(MAX_SIZE).listener(listener.build();
私有静态void deleteEvents(){
内部容量=最大尺寸-(最大尺寸*(20/100));
if(holder.size()>=容量){
int numEventsToEvict=(最大尺寸*20)/100;
int计数器=0;
迭代器iter=holder.keySet().Iterator();
while(iter.hasNext()&计数器
下面是我用于此目的的maven依赖项:

<dependency>
    <groupId>com.googlecode.concurrentlinkedhashmap</groupId>
    <artifactId>concurrentlinkedhashmap-lru</artifactId>
    <version>1.4</version>
</dependency>

com.googlecode.concurrentlinkedhashmap
concurrentlinkedhashmap lru
1.4
我不确定这样做是否正确?如果事件的平均大小为200字节,那么最大大小是否真的等于3.7 GB?有没有更好的办法?我还有一个后台线程,它每30秒调用一次
deleteEvents()
方法,同一个后台线程还调用
submit
方法从
holder
映射提取数据并发送到队列

其思想是,在
addToHolderRequest
方法中将事件添加到
holder
映射中,然后每隔30秒从后台调用
submit
方法,该方法将通过迭代此映射将数据发送到我们的队列,然后在submit方法完成后,调用
deleteEvents()
方法,该方法来自将清除元素的同一后台线程。我正在生产环境中运行这段代码,看起来它没有正确地清除事件,并且我的持有者映射大小一直在增长。我将最小/最大堆内存设置为6GB

  • 代替估计JVM中对象的大小并使用强引用引用它们,您可以使用软引用,软引用“最常用于实现内存敏感缓存”()。e、 g.from:“软引用对象将以全球最近使用最少的方式进行垃圾收集,以响应内存需求。”不过,我建议您首先熟悉一下(特别是本节)
  • 作为对使用软引用的一种调整,您还可以尝试一种“受害者缓存方法”,如前所述,该方法使用“退出到[a]软缓存的正常缓存,并在可能的情况下恢复未命中的条目”
  • 如果您确定要实际估计对象的大小,请查看其大小。它具有内存有限的缓存

  • 堆上的Java对象大小非常棘手,而且容易测量错误。如果平均200字节的假设成立,我会怀疑,这取决于它是如何计算的。奇怪的是,你的听众试图清除地图,这似乎是多余的。大规模缓存也会受到GC影响,例如所有条目都会被提升,并且可能需要更昂贵的(例如STW)收集。但是,推荐的替代方案取决于您的应用程序。@BenManes那么我应该怎么做?在我的例子中,我只需要开始从映射中重新移动元素,如果它达到特定的内存限制?或者我可以限制映射中的元素数量,但是映射中的元素数量应该小于堆内存大小,堆内存大小现在设置为6GB。如果这个假设不成立,那么我该如何计算呢?我的想法是,我不希望我的
    holder
    映射不断增长并最终耗尽内存,所以只需开始从LRU缓存中删除最旧的元素。顺便说一句,我使用的是Java7。唯一衡量保留多少的方法是在完全GC之后。否则,您不知道如果运行完整GC,将保留多少。显然,您必须包括每个条目使用的所有内存,其中包括Map.entry、键的大小及其对象头、值的大小及其对象头。这很容易达到64字节或更多。在ConcurrentHastMap上使用迭代器是一种非常糟糕的逐出条目的方法。迭代器将从同一段开始,因此您将从第一段删除,而最后一段可能永远不会逐出条目。您最好随机删除条目。理想情况下,您应该使用一个条目队列来删除。