2013-05-02 96 views
1

我遇到了一些与我的项目有关的内存问题,因此我决定对某些部分进行压力测试以查看一些性能测量结果。我使用Google的ConcurrentLinkedHashMap库作为LRU内存缓存。我的测试代码的相关部分如下图所示:强制清除内存

final ConcurrentLinkedHashMap cache = new ConcurrentLinkedHashMap.Builder<Long,Long>() 
      .maximumWeightedCapacity(200000) 
      .build(); 

for (int i = 0; i < 10; i++) { 
    new Thread(new Runnable() { 
     @Override 
     public void run() { 
      int count = 0; 
      while (count < 1000000) { 
       if (throttle) { 
        Thread.sleep(1000); 
        continue; 
       } 
       cache.put(random.nextLong(), random.nextLong()); 
       count++; 
      } 
     } 
    }).start(); 
} 

this.wait(); 

我的throttle标志设置为true一旦存储器命中超过50%。我有一个监测线程,每隔2秒进行一次测量。下面是我得到的数字:

Size: 423902 Weighted Size: 200001 Memory: 0.11229571913729916 
Size: 910783 Weighted Size: 200001 Memory: 0.25812696264655144 
Size: 1373394 Weighted Size: 200001 Memory: 0.38996117352719034 
Size: 2120239 Weighted Size: 200001 Memory: 0.6203725762957892 
Size: 2114424 Weighted Size: 200000 Memory: 0.6233790564491212 
Size: 2114424 Weighted Size: 200000 Memory: 0.6233790564491212 
Size: 2114424 Weighted Size: 200000 Memory: 0.6233790564491212 
Size: 2114424 Weighted Size: 200000 Memory: 0.6233790564491212 
Size: 2114424 Weighted Size: 200000 Memory: 0.6233790564491212 
Size: 2114424 Weighted Size: 200000 Memory: 0.6233790564491212 

我没有看到的LRU缓存的evicted条目被清理出于某种原因。我听说手动呼叫System.gc()是一个坏主意。如果是这样,有效清理内存的好方法是什么?

一边注意:有没有人知道size()ConcurrentLinkedHashMap返回? weightedSize()返回正确的值,但我担心size返回的东西明显更大..

+0

你如何获得统计信息?例如:大小:423902加权大小:200001内存:0.11229571913729916 ... – fuyou001 2013-05-07 10:06:16

回答

3

正如你的数字显示的那样,缓存并不严格保证最大值,但会尝试保持高水位。如果确实做出了有力的保证,那么它会通过对每个写入操作进行阻止调用来限制并发性,并且无法有效地维护顶级LRU策略。在Guava的Cache中,我们通过使用锁定条带来做到这一限制,但这种权衡带有限制。

由于CLHM异步连接散列表和LRU数据结构,所以这两个数字可能会偏离。 size由装饰ConcurrentHashMap测量。这是在任何给定时刻键值对的最准确估计值。 是由LRU策略在针对其数据结构重播映射操作以确定新近度排序和执行驱逐时计算的。加权的大小保持在预期的最大值,因为当超过阈值时策略将被驱逐。

预计突发大小不同,但缓存将始终尝试快速自行纠正。在您的示例中,行为会加剧,因为操作系统会为大量时间片安排线程。这使得它可以在真实世界的场景中执行比可能更多的插入操作。摊销的LRU管理意味着驱逐无法跟上,因为它在任何特定时刻从一个线程窃取了一段时间。为了更好地模拟实际行为,MemoryLeakTest强制在操作之间切换上下文。

如果您实施驱逐监听器,您应该看到计数增加并且您已达到稳定状态。如果你强制上下文切换,你应该看到大小保持在一个下限。如果您希望对地图中可能的数字条目进行更严格的限制,那么首选Guava实施。

+0

感谢您的意见。我知道测试有点极端,我的实际应用可能每秒平均只能做50次插入。然而,我不明白为什么当我“扼杀”所有线程时(当应用程序检测到> 50%的内存使用率时),JVM不会使用“清除”从LRU映射中清除的项目的机会。 – Jin 2013-05-02 18:30:47

+0

或者,相反,LRU映射并没有使用实际清除映射的机会,所以'size()'将等于'weightedSize()' – Jin 2013-05-02 18:31:35

+0

实际上,接受这个作为答案。看着我的统计日志,我每秒钟执行500次插入。但是,在每次插入之前添加一个Thread.sleep(2)似乎会为缓存提供足够的时间来自动进行自我纠正。所以看来,这不是我正在寻找的问题。谢谢! (如果你对我之前关于为什么LRU缓存最终不会自行清除的问题有一个回答,请告诉我) – Jin 2013-05-02 19:04:18

0

是不是默认的驱逐政策先进先出?如果是这样,你看第一次插入看到他们被驱逐?如果你正在看最后一个,你将不会看到它们。

这是从文档:默认情况下,根据我的阅读它

 
The default 
* weigher assigns each value a weight of 1 to bound the map by the 
* total number of key-value pairs. A map that holds collections may choose to 
* weigh values by the number of elements in the collection and bound the map 
* by the total number of elements that it contains. A change to a value that 
* modifies its weight requires that an update operation is performed on the 
* map. 

所以加权总规模不会改变。

+0

我不是,我只是假定LRU策略如上所述。我只是随机生成'long'对象来插入。我将如何检查他们是否被驱逐?我认为他们被驱逐,因为'weightedSize()'计数很好(200000) – Jin 2013-05-02 02:51:40

+0

但是,为什么你会期望加权大小发生变化?一旦你击中它,一件物品将被逐出(基于政策),因为一件物品被添加。所以加权大小将保持不变。 – CBass 2013-05-02 02:57:48

+0

确切地说,所以我假设既然'weightedSize()'是预期的,那么之前的项目就会按照预期逐出。 – Jin 2013-05-02 03:00:50