<< Previous | Next >>

Detecting memory pressure in the JVM

Because java is garbage collected, detecting how you are doing memory wise is difficult. You can use the Runtime.freeMemory() and other methods, but they count all memory, ignoring all garbage.

So ideally, what you want to know is how the memory was doing just after the last garbage collection round. Internally, the jvm has the cpp function Universe::get_heap_used_at_last_gc() which could be used for that, but it is not exposed anywhere (AFAIK).

So why would you need to the your exact memory situation? I guess a few reason like tuning and flushing caches, load balancing, flushing data (to disk or servers), and statistics.

To accomplish these, there are a few tools you can use:

So here is another way to detect memory pressure in the jvm, a PressureValve. It works by rigging a SoftReference internal timestamp field to make it appear older, just old enough to get collected after the free memory drops below a certain amount megabytes, the threshold.

Everytime you readout() the PressureValve it will setup a new SoftReference, and it works best if you poll it at regular intervals. Likely often enough so that you prevent OutOfMemoryErrors, if you use it for tuning caches, or flushing data.

It is totally implementation specific, and requires making some internal fields to be made accessable. It assumes a default -XX:SoftRefLRUPolicyMSPerMB=1000 java option, and works best in -server mode. But it works with all collectors.

class PressureValve {
    static enum State { going_below, going_above, nothing };

    private SoftReference<Object> valve;
    private long threshold;
    private long lastClock;
    private State prevState = State.nothing;


    // threshold is expressed as the amount 
    // of free megabytes that must be in the system
    PressureValve(long threshold) {
        this.threshold = threshold;
        lastClock = getClock();
        reset();
    }

    // readout if the set threshold has been reached,
    // and reset the internal SoftReference after reading
    State readout() {
        State s = State.nothing;
        long now = getClock();

        if (valve.get() == null) {
            s = State.going_above;
            lastClock = now;
            prevState = s;
        } else if (lastClock != now && prevState != State.going_below) {
            // if the value holds, and the clock has
            // been updated, we survived the pressure
            s = State.going_below;
            prevState = s;
        }

        reset();
        return s;
    }

    // setup a new rigged SoftReference object
    private void reset() {
        valve = new SoftReference<Object>(new Object());
        setTimestamp(getClock() - (threshold * 1000));
    }


    // ** exposing the SoftReference internals **

    private static Field f_timestamp;
    private static Field f_clock;
    static {
        try {
            f_clock = SoftReference.class.getDeclaredField("clock");
            f_clock.setAccessible(true);
            f_timestamp = SoftReference.class.getDeclaredField("timestamp");
            f_timestamp.setAccessible(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private long getClock() {
        try {
            return f_clock.getLong(valve);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return -1;
    }

    private void setTimestamp(long ts) {
        try {
            f_timestamp.setLong(valve, ts);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

Good thing java is open-source! Otherwise this could not have been done.

Here is why it works, in hotspot/src/share/vm/memory/referencePolicy.cpp:

LRUMaxHeapPolicy::LRUMaxHeapPolicy() {
    size_t max_heap = MaxHeapSize;
    max_heap -= Universe::get_heap_used_at_last_gc();
    max_heap /= M;
 
    _max_interval = max_heap * SoftRefLRUPolicyMSPerMB;
    assert(_max_interval >= 0,"Sanity check");
}

// The oop passed in is the SoftReference object, and not
// the object the SoftReference points to.
bool LRUMaxHeapPolicy::should_clear_reference(oop p) {
    jlong interval = java_lang_ref_SoftReference::clock() -
           java_lang_ref_SoftReference::timestamp(p);
    assert(interval >= 0, "Sanity check");

    // The interval will be zero if the ref was accessed since the last scavenge/gc.
    if(interval <= _max_interval) {
        return false;
    }

    return true;
}

Last modified: 2008-05-20 16:09 GMT