30 October 2006

Memory notifications in Java

If you're like me, then you probably relied on the venerable Runtime.freeMemory() call to figure out how you application or container was doing. Especially if, (ahem) you might have suspected a memory leak (or two) at some point.

Well, JDK 1.5 has some fancy new stuff that's really nifty.

First off, you get a good picture of what your memory looks like (both heap and non-heap) like so:

MemoryMXBean memBean = ManagementFactory.getMemoryMXBean();
MemoryUsage heap = memBean.getHeapMemoryUsage();
MemoryUsage nonHeap = memBean.getNonHeapMemoryUsage();
System.out.println(heap);
System.out.println(nonHeap);

This gets you some fairly cool (and very bash-script analyzable) data:

init = 33161792(32384K) used = 301960(294K)
committed = 33226752(32448K) max = 512950272(500928K)
init = 19136512(18688K) used = 1913488(1868K)
committed = 19136512(18688K) max = 117440512(114688K)


Now, on to more interactive things:

public class Mem implements NotificationListener {

public void handleNotification(Notification n, Object hb) {
//-- we'll get to this in a bit
}

public static void main (String[] args) {
Mem mem = new Mem();
MemoryMXBean memBean = ManagementFactory.getMemoryMXBean();
NotificationEmitter ne = (NotificationEmitter)memBean ;
ne.addNotificationListener(mem, null, null);

//-- more to come: configure thresholds
}
}


The above class implements the javax.management.NotificationListener interface that defines the handleNotification method. Now, the main method fetches the MemoryMXBean which turns out to also be a NotificationEmitter (javadocs come in real handy). You could actually set up notifications where you define both filters and user objects can be applied and handed into the handleNotification call. We'll be amateurs and opt to filter nothing and not forward any of our objects.

With the notification mechanism in place, we need set up a threshold to be notified on. Expanding the main method:


public static void main (String[] args) {
Mem mem = new Mem();
MemoryMXBean memBean = ManagementFactory.getMemoryMXBean();
NotificationEmitter ne = (NotificationEmitter)memBean ;
ne.addNotificationListener(mem, null, null);

List memPools = ManagementFactory.getMemoryPoolMXBeans();
for (Iterator i = memPools.iterator(); i.hasNext();) {
MemoryPoolMXBean mp = i.next();
if (mp.isUsageThresholdSupported() ) {
// Found the heap! Let's add a notifier
MemoryUsage mu = mp.getUsage();
long max = mu.getMax();
long alert = (max * 50)/100;
System.out.println("Setting a warning on pool: " + mp.getName() + " for: " + alert);
mp.setUsageThreshold(alert);
}
}


We can call for all the MemoryPoolMXBeans, and then check to see if threshold setting is supported. For those that we can configure, we set an alert for when we surpass 50% usage. At this point, you are in some pretty rarefied territory within the VM:

Setting a warning on pool: Code Cache for: 25165824
Setting a warning on pool: PS Old Gen for: 236748800
Setting a warning on pool: PS Perm Gen for: 33554432


So, can we avert the dreaded out of memory condition?

Here's our uber-malicious memory leak:

import java.util.*;

public class Leak extends Thread {

public static boolean keepLeaking = true;

public void run () {
Map map = new HashMap();
int x = 0;
while (keepLeaking) {
String key = new String(""+x*10000);
String value = new String (""+x*243523);
map.put(key, value);
try { Thread.sleep(1); } catch (Exception e) {}
x++;
}
}
}


A call to set keepLeaking to false can avert a sure VM memory issue. So, here's the fleshed out handleNotification method:

public void handleNotification(Notification n, Object hb) {
String type = n.getType();
if (type.equals(MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED)) {
// retrieve the memory notification information
CompositeData cd = (CompositeData) n.getUserData();
MemoryNotificationInfo memInfo = MemoryNotificationInfo.from(cd);
System.out.println(memInfo.getPoolName() + " has exceeded the threshold : " +
memInfo.getCount() + " times");
System.out.println(memInfo.getUsage());
Leak.keepLeaking = false;
} else {
System.out.println("Unknown notification: " + n);
}
}


And, presto:


$ java -Xmx8m Mem
Setting a warning on pool: Code Cache for: 25165824
Setting a warning on pool: PS Old Gen for: 3735552
Setting a warning on pool: PS Perm Gen for: 33554432
Leaks started
PS Old Gen has exceeded the threshold : 1 times
init = 1441792(1408K) used = 3763520(3675K) committed = 4849664(4736K) max = 7471104(7296K)
$


You'd probably want to reset the peak usage a few times and ensure that you see a consistent incursion before doing anything dramatic (give the GC a chance..). But, this is a fairly large leap from the prior polling mechanism that we would have had to adopt to monitor how a VM is doing. Now, we can code the reaction to a situation with set thresholds instead of managing polling threads.

20 October 2006

List.remove

Here's a really simple one:


...
public static void main (String[] args) {
List <String> l = new ArrayList<String>();
String one = new String("one");
System.out.println(l.size());
l.add(one);
l.add(one);
System.out.println(l.size());
l.remove(one);
System.out.println(l.size());
}
...


That gets you this:


0
2
1


Well, it isn't rocket science. This isn't a Set, it is a List. And the method remove says:

Removes the first occurrence in this list of the specified element (optional operation). If this list does not contain the element, it is unchanged. More formally, removes the element with the lowest index i such that (o==null ? get(i)==null : o.equals(get(i))) (if such an element exists).


The block that does all the hard work in the ArrayList class looks like so:


for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}



So, if you want your output to look like so:

0
2
0


Change your list's remove to look like this:

while (l.remove(one));


Or better yet, use a Set.

19 October 2006

06 October 2006

Playing with NIO

I had heard a lot about how fast NIO was. So, I decided to find out. I picked up my BufferedReader benchmarking program and used it as a baseline. Then, I wrote the following to test how fast NIO ran:


....
File f = new File("USA.txt");
FileInputStream fin = new FileInputStream(f);
FileChannel fc = fin.getChannel();
long size = fc.size();
ByteBuffer bb = ByteBuffer.allocate(2048);
long start = System.currentTimeMillis();
while ( (fc.read(bb)) > 0 ){
bb.flip();
String s = new String(bb.array());
}
long finish = System.currentTimeMillis();
fc.close();
.....


With about a 100 runs, the two stack up pretty evenly to read in a file of about 3 MB.



Note that the first few runs see NIO slightly slower, and then it hits a step and speeds up again. Perhaps being a lot closer to the OS, you can actually see the contents of the file move through different cache layers as we hit it more.. Not sure.

This wasn't what I had expected. Perhaps the overhead on string creation was dwarfing the real gains in NIO. Or perhaps NIO performs better when the files to ingest are larger. So, I ran the benchmarks again with a file of about 200 MB.



NIO saves us 1 second over BufferedReader, which executes at approximately 5 seconds. Depending on whose side you are on, BufferedReader costs only 20% more, or NIO generates a savings of 25%. Regardless, NIO is faster. But, it is a little more complex too.

I don't know that the speed boost (specifically for File IO) warrants a switch to NIO. File locking sure seems nice, and it would be interesting to try and use NIO over a network to see if things change more dramatically there. Another point to keep in mind is that I used the allocate method in my NIO code, not the allocateDirect, which can optimize even further. However, in my scenario, the allocateDirect did not execute without a runtime exception.

To sum things up, NIO ran faster. The larger the file size, the greater the impact. For small files though, NIO could quite easily be overkill, both on the performance end, and the code maintenance end.