|
The Java Specialists' Newsletter
Issue 092 2004-07-20
Category:
Performance
Java version: Sun JDK 1.5.0-beta2 OutOfMemoryError Warning Systemby Dr. Heinz M. Kabutz
Welcome to the 92nd edition of The Java(tm) Specialists' Newsletter. Down here at the
end of Africa, tech-toys are rather expensive, so whilst I
was in Germany, I purchased a 802.11g wireless router. It is
linked to my ADSL line (which costs about US$ 160 per month),
allowing me to surf the internet at 512kb/s whilst sitting
next to my pool. Naturally, I was rather proud with my
purchase. On the last day of my visit to Germany, I popped
in at my aunt & uncle to chase some golf balls across the
meadows (I lost about 12 balls in 18 holes!) Imagine my
surprise when my uncle told me matter-of-factly, "Oh, by the
way, we have a wireless network at home linked to DSL".
Ahem, but that is not all. My grandmother (at the age of 91
years) now also has a wireless network at home. My Oma
uses the wireless network so that she can sit in her garden
and write emails on her notebook to her grandchildren.
How's that?!
On another topic, there is a cool utility from Sun called jvmstat
that will show you the various generational memory pools and how
much time is being spent by the garbage collectors. I linked
jvmstat's VisualGC to my IntelliJ IDEA and was surprised that
in 2.5 days, it had only used about 3 minutes for collecting
garbage! It shows you the Eden space, the Survivor spaces,
the Old Generation and the Permanent Generation.
Upcoming Java Specialist Master Courses:
- please click here to sign up.
As from May 2010, we are also offering this course on the island of Crete. We
only accept 6 students per class in Crete, due to the size of our conference
room. Please book early to avoid disappointment!
San Jose CA, Mar 16-19 2010, $3500 Ottawa, Canada, Mar 22-25 2010, $3500 Oslo, Norway, Apr 13-16 2010, Kr 24500 Montreal, Canada, Apr 20-23 2010, $3500 Toronto, Canada, May 17-20 2010, $3500 Chania, Crete, May 25-28, Jun 29-Jul 2 or Aug 24-27 2010, €2500
In-house courses if these dates or locations do not suit you - click here for more information. OutOfMemoryError Warning System
In Issue
061 of my newsletter, I asked readers whether their
applications had ever caused an OutOfMemoryError. I then
asked them to email me if they would like to know how to
receive a warning, shortly before it was about to happen.
Wow, the response! The requests kept on pouring in, and so far, I
have had over 200 enquiries. At the time,
my ideas for a warning system were sketchy at best, and would
have been hopelessly inaccurate, in comparison to what JDK 1.5
offers us.
JDK 1.5 has added some wonderful new management beans that
make writing an OutOfMemoryError Warning System possible.
The most difficult part was probably finding resources
on the topic. Google turned up two resources: The JDK
documentation and a website
written in Japanese ;-)
An OutOfMemoryError (OOME) is bad. It can happen at any
time, with any thread. There is little that you can do about
it, except to exit the program, change the -Xmx value, and
restart the JVM. If you then make the -Xmx value too large,
you slow down your application. The secret is to make the
maximum heap value the right size, neither too
small, nor too big. OOME can happen with any thread, and
when it does, that thread typically dies. Often, there is
not enough memory to build up a stack trace for the
OOME, so you cannot even determine where it occurred, or
why. You can use the exception catching mechanism of
Issue 089, but that is
then an after-the-fact measure, rather than preventative.
In January this year, I was migrating a program from MySQL
to MS SQL Server. The author of the original program had
used some JDBC commands that caused memory leaks under the
SQL Server JDBC driver. This meant that periodically, one of
the application's threads would simply vanish, leaving parts
of the system paralyzed. Eliminating the OOME was a major
task, and only happened when I rewrote all the database
access code!
Back to the issue at hand - how can we know when OOME's are
about to occur? The answer lies in the java.lang.management
package of JDK 1.5. The
ManagementFactory class returns all sorts of
useful JMX beans that we can use to manage the JVM. One of
these beans is the MemoryMXBean.
Sun's implementation of
the MemoryMXBean interface also implements the interface
javax.management.NotificationEmitter. The
recommended way of listening to notifications by the memory
bean is by downcasting the MemoryMXBean to the NotificationEmitter
interface. I can hardly believe it myself, you can verify this by
looking at the
documentation
of the MemoryMXBean.
Once you have downcast the MemoryMXBean to a
NotificationEmitter, you can add a NotificationListener to
the MemoryMXBean. You should verify that the notification is
of type MEMORY_THRESHOLD_EXCEEDED. In my MemoryWarningSystem
you add listeners that implement the MemoryWarningSystem.Listener
interface, with one method
memoryUsageLow(long usedMemory, long maxMemory)
that will be called when the threshold is reached. In my
experiments, the memory bean notifies us quite soon after the
usage threshold has been exceeded, but I could not determine
the granularity. Something to note is that the listener is
being called by a special thread, called the Low Memory
Detector thread, that is now part of the standard JVM.
What is the threshold? And which of the many pools should
we monitor? The only sensible pool to monitor is the Tenured
Generation (Old Space). When you set the size of the memory
with -Xmx256m, you are setting the maximum memory to be used in the
Tenured Generation. I could not find a neat way of
finding the tenured generation, except by looking through all
the pools in my findTenuredGenPool() method, and
returning the first one that was of type HEAP and where I was
permitted to specify a usage threshold. I do not know whether
a better approach would not have been to search for the name
"Tenured Gen"?
In my setPercentageUsageThreshold(double percentage)
method, I specify when I would like to be notified. Note
that this is a global setting since you can only have one
usage threshold per Java Virtual Machine. The percentage is
used to calculate the usage threshold, based on the maximum
memory size of the Tenured Generation pool (not the
Runtime.getRuntime().maxMemory() value!).
import javax.management.*;
import java.lang.management.*;
import java.util.*;
/**
* This memory warning system will call the listener when we
* exceed the percentage of available memory specified. There
* should only be one instance of this object created, since the
* usage threshold can only be set to one number.
*/
public class MemoryWarningSystem {
private final Collection<Listener> listeners =
new ArrayList<Listener>();
public interface Listener {
public void memoryUsageLow(long usedMemory, long maxMemory);
}
public MemoryWarningSystem() {
MemoryMXBean mbean = ManagementFactory.getMemoryMXBean();
NotificationEmitter emitter = (NotificationEmitter) mbean;
emitter.addNotificationListener(new NotificationListener() {
public void handleNotification(Notification n, Object hb) {
if (n.getType().equals(
MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED)) {
long maxMemory = tenuredGenPool.getUsage().getMax();
long usedMemory = tenuredGenPool.getUsage().getUsed();
for (Listener listener : listeners) {
listener.memoryUsageLow(usedMemory, maxMemory);
}
}
}
}, null, null);
}
public boolean addListener(Listener listener) {
return listeners.add(listener);
}
public boolean removeListener(Listener listener) {
return listeners.remove(listener);
}
private static final MemoryPoolMXBean tenuredGenPool =
findTenuredGenPool();
public static void setPercentageUsageThreshold(double percentage) {
if (percentage <= 0.0 || percentage > 1.0) {
throw new IllegalArgumentException("Percentage not in range");
}
long maxMemory = tenuredGenPool.getUsage().getMax();
long warningThreshold = (long) (maxMemory * percentage);
tenuredGenPool.setUsageThreshold(warningThreshold);
}
/**
* Tenured Space Pool can be determined by it being of type
* HEAP and by it being possible to set the usage threshold.
*/
private static MemoryPoolMXBean findTenuredGenPool() {
for (MemoryPoolMXBean pool :
ManagementFactory.getMemoryPoolMXBeans()) {
// I don't know whether this approach is better, or whether
// we should rather check for the pool name "Tenured Gen"?
if (pool.getType() == MemoryType.HEAP &&
pool.isUsageThresholdSupported()) {
return pool;
}
}
throw new AssertionError("Could not find tenured space");
}
}
I have tested this with a small program called MemTest. It
sets the threshold to 60%, then prints out a message when
that is reached, and changes the threshold to 80%. The main
thread of the program puts random Double objects into a
LinkedList and just keeps on going until it runs out of
memory.
import java.util.*;
public class MemTest {
public static void main(String[] args) {
MemoryWarningSystem.setPercentageUsageThreshold(0.6);
MemoryWarningSystem mws = new MemoryWarningSystem();
mws.addListener(new MemoryWarningSystem.Listener() {
public void memoryUsageLow(long usedMemory, long maxMemory) {
System.out.println("Memory usage low!!!");
double percentageUsed = ((double) usedMemory) / maxMemory;
System.out.println("percentageUsed = " + percentageUsed);
MemoryWarningSystem.setPercentageUsageThreshold(0.8);
}
});
Collection<Double> numbers = new LinkedList<Double>();
while (true) {
numbers.add(Math.random());
}
}
}
The output on my machine is, for example:
Memory usage low!!!
percentageUsed = 0.6281726667795322
Memory usage low!!!
percentageUsed = 0.8504659318016649
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Since constructing new objects is a rather fast operation in
Java, it may happen that the JVM runs out of memory before
getting a chance to notify us.
Inside your listener, you can then do some preventative
measures. For example, you could print out what each of the
threads in your system was doing, using
Thread.getAllStackTraces(). Or, you could keep
a nice big chunk of memory available as reserve, and then
release that when you are running low on memory, and send a
warning message to the system administrator.
The ability to at least get all the stack traces will make
it much easier to find & destroy memory leaks in Java.
Kind regards
Heinz
Performance Articles
Related Java Course
|