Java fixed memory map
Asked Answered
H

5

14

Is there a simple, efficient Map implementation that allows a limit on the memory to be used by the map.

My use case is that I want to allocate dynamically most of the memory available at the time of its creation but I don't want OutOFMemoryError at any time in future. Basically, I want to use this map as a cache, but but I wanna avoid heavy cache implementations like EHCache. My need is simple (at most an LRU algorithm)

I should further clarify that objects in my cache are char[] or similar primitives that will not hold references to other objects.

I can put an upper limit on max size for each entry.

Headlock answered 31/5, 2010 at 3:50 Comment(1)
From how i understand your question, your idea is to allocate a dedicated amount of memory at startup for caching. The problem with that is, you cant know the type of memory in advance (We are talking about caching objects here, right?). Unlike C, you cant simple allocate a block of memory and then cast a pointer into a subsection of that to be treated as object X. So for generalized solutions, fixed memory caches are not practical in Java. Also your assumption that this could protect you from OutOfMemoryErrors is questionable at best.Deserving
H
0

thanks for replies guys!

As jasonmp85 pointed out LinkedHashMap has a constructor that allows access order. I missed out that bit when I looked at API docs. The implementation also looks quite efficient(see below). Combined with max size cap for each entry, that should solve my problem.

I will also look closely at SoftReference. Just for the record, Google Collections seems to have pretty good API for SoftKeys and SoftValues and Maps in general.

Here is a snippet from Java LikedHashMap class that shows how they maintain LRU behavior.

    /**
     * Removes this entry from the linked list.
     */
    private void remove() {
        before.after = after;
        after.before = before;
    }

    /**
     * Inserts this entry before the specified existing entry in the list.
     */
    private void addBefore(Entry<K,V> existingEntry) {
        after  = existingEntry;
        before = existingEntry.before;
        before.after = this;
        after.before = this;
    }

    /**
     * This method is invoked by the superclass whenever the value
     * of a pre-existing entry is read by Map.get or modified by Map.set.
     * If the enclosing Map is access-ordered, it moves the entry
     * to the end of the list; otherwise, it does nothing.
     */
    void recordAccess(HashMap<K,V> m) {
        LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
        if (lm.accessOrder) {
            lm.modCount++;
            remove();
            addBefore(lm.header);
        }
Headlock answered 2/6, 2010 at 21:19 Comment(2)
Apparently yes. Oh well. At least I'm in the habit of upvoting everyone else on threads I'm in (assuming their answers are remotely helpful and not egregiously incorrect).Rydder
sorry guys, this is the first time I have posted a question on StackOverflow. They wouldn't let me vote since I am not registered. I accepted my own answer because there I summarized the answers that I found helpful. And Joson, yes your answer was most helpful, thanks.Headlock
F
14

You can use a LinkedHashMap to limit the number of entries in the Map:

removeEldestEntry(Map.Entry<K,V> eldest): Returns true if this map should remove its eldest entry. This method is invoked by put and putAll after inserting a new entry into the map. It provides the implementor with the opportunity to remove the eldest entry each time a new one is added. This is useful if the map represents a cache: it allows the map to reduce memory consumption by deleting stale entries.

Sample use: this override will allow the map to grow up to 100 entries and then delete the eldest entry each time a new entry is added, maintaining a steady state of 100 entries.

private static final int MAX_ENTRIES = 100;

protected boolean removeEldestEntry(Map.Entry eldest) {
    return size() > MAX_ENTRIES;
}

Related questions

Flinger answered 31/5, 2010 at 3:53 Comment(3)
The WeakHashMap will remove the entry once no strong reference to the key is hold. That doesn't sound like a good fit for cache. Also if enough strong reference are hold to the key, you will still see OOME. So I don't understand how a WeakHashMap could help here..Carlstrom
@Zwei: I just kept it there as possibility; I've removed it now, exclusively leaving LinkedHashMap as an option.Flinger
@juber: one of the related question mentions org.apache.commons.collections.map.LRUMap; you can check that out. I'm not actually familiar with it.Flinger
P
7

For caches, a SoftHashMap is much more appropriate than a WeakHashMap. A WeakhashMap is usually used when you want to maintain an association with an object for as long as that object is alive, but without preventing it from being reclaimed.

In contrast, a SoftReference is more closely involved with memory allocation. See No SoftHashMap? for details on the differences.

WeakHashMap is also not usually appropriate as it has the association around the wrong way for a cache - it uses weak keys and hard values. That is, the key and value are removed from the map when the key is cleared by the garbage collector. This is typically not what you want for a cache - where the keys are usually lightweight identifiers (e.g. strings, or some other simple value type) - caches usually operate such that the key/value is reclaimed when the value reference is cleared.

The Commons Collections has a ReferenceMap where you can plug in what types of references you wish to use for keys and values. For a memory-sensitive cache, you will probably use hard references for keys, and soft references for values.

To obtain LRU semantics for a given number of references N, maintain a list of the last N entries fetched from the cache - as an entry is retrieved from the cache it is added to the head of the list (and the tail of the list removed.) To ensure this does not hold on to too much memory, you can create a soft reference and use that as a trigger to evict a percentage of the entries from the end of the list. (And create a new soft reference for the next trigger.)

Pluperfect answered 31/5, 2010 at 4:6 Comment(0)
R
3

Java Platform Solutions

If all you're looking for is a Map whose keys can be cleaned up to avoid OutOfMemoryErrors, you might want to look into WeakHashMap. It uses WeakReferences in order to allow the garbage collector to reap the map entries. It won't enforce any sort of LRU semantics, though, except those present in the generational garbage collection.

There's also LinkedHashMap, which has this in the documentation:

A special constructor is provided to create a linked hash map whose order of iteration is the order in which its entries were last accessed, from least-recently accessed to most-recently (access-order). This kind of map is well-suited to building LRU caches. Invoking the put or get method results in an access to the corresponding entry (assuming it exists after the invocation completes). The putAll method generates one entry access for each mapping in the specified map, in the order that key-value mappings are provided by the specified map's entry set iterator. No other methods generate entry accesses. In particular, operations on collection-views do not affect the order of iteration of the backing map.

So if you use this constructor to make a map whose Iterator iterates in LRU, it becomes pretty easy to prune the map. The one (fairly big) caveat is that LinkedHashMap is not synchronized whatsoever, so you're on your own for concurrency. You can just wrap it in a synchronized wrapper, but that may have throughput issues.

Roll Your Own Solution

If I had to write my own data structure for this use-case, I'd probably create some sort of data structure with a map, queue, and ReadWriteLock along with a janitor thread to handle the cleanup when too many entries were in the map. It would be possible to go slightly over the desired max size, but in the steady-state you'd stay under it.

Rydder answered 31/5, 2010 at 3:55 Comment(1)
I also just came across Google Collections MapMaker: google-collections.googlecode.com/svn/trunk/javadoc/com/google/…. It looks pretty useful.Rydder
C
1

WeakHashMap won't necessarily attain your purpose since if enough strong reference to the keys are hold by your app., you WILL see OOME.

Alternatively you could look into SoftReference, which will null out the content once the heap is scarce. However, most of the comments I seen indicate that it will not null out the reference until the heap is really really low and a lot of GC starts to kick in with severe performance hit (so I don't recommend using it for your purpose).

My recommendation is to use a simple LRU map, e.g. http://commons.apache.org/collections/apidocs/org/apache/commons/collections/LRUMap.html

Carlstrom answered 31/5, 2010 at 4:9 Comment(0)
H
0

thanks for replies guys!

As jasonmp85 pointed out LinkedHashMap has a constructor that allows access order. I missed out that bit when I looked at API docs. The implementation also looks quite efficient(see below). Combined with max size cap for each entry, that should solve my problem.

I will also look closely at SoftReference. Just for the record, Google Collections seems to have pretty good API for SoftKeys and SoftValues and Maps in general.

Here is a snippet from Java LikedHashMap class that shows how they maintain LRU behavior.

    /**
     * Removes this entry from the linked list.
     */
    private void remove() {
        before.after = after;
        after.before = before;
    }

    /**
     * Inserts this entry before the specified existing entry in the list.
     */
    private void addBefore(Entry<K,V> existingEntry) {
        after  = existingEntry;
        before = existingEntry.before;
        before.after = this;
        after.before = this;
    }

    /**
     * This method is invoked by the superclass whenever the value
     * of a pre-existing entry is read by Map.get or modified by Map.set.
     * If the enclosing Map is access-ordered, it moves the entry
     * to the end of the list; otherwise, it does nothing.
     */
    void recordAccess(HashMap<K,V> m) {
        LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
        if (lm.accessOrder) {
            lm.modCount++;
            remove();
            addBefore(lm.header);
        }
Headlock answered 2/6, 2010 at 21:19 Comment(2)
Apparently yes. Oh well. At least I'm in the habit of upvoting everyone else on threads I'm in (assuming their answers are remotely helpful and not egregiously incorrect).Rydder
sorry guys, this is the first time I have posted a question on StackOverflow. They wouldn't let me vote since I am not registered. I accepted my own answer because there I summarized the answers that I found helpful. And Joson, yes your answer was most helpful, thanks.Headlock

© 2022 - 2024 — McMap. All rights reserved.