Name: gm110360 Date: 01/29/2004
When dealing with a reasonably large application, the JVM can take an
hour or more to provide a heap dump when requested. Further, the JVM
remains unresponsive for an indeterminate amount of time after the JVMPI
client has finished operations with the heap dump -- leaving the Java
application unresponsive for hours.
Environment:
The problems do not seem specific to OS version or VM -- Solaris 7/8/9
have been tried with JDK 1.3.1_08, 1.4.0_03, 1.4.1_02, and 1.4.2.
How to repeat:
1) Attached is a class called HeapThrasher -- save it and compile it.
2) Run the application on Solaris using a 1.3.1 or newer JVM and the
following command line (current structures in the application will
require a 512MB heap -- see the source code if you intend to adjust the
heap size):
java -Xrunhprof:heap=all -Xms512M -Xmx512M HeapThrasher
3) Wait for the application to 'settle' -- eventually it will print a
message every 15 seconds reporting heap size. Let the application run
for another hour or so (it will churn away, allocating and releasing
objects).
4) Request a heap dump -- send the process a SIGQUIT
The amount of time it takes to generate a dump does not directly relate
to the size of the heap -- the attached application will take upwards of
30 minutes to generate a heap dump and continue processing. Application
servers that have been running for a day or more may take hours with a
heap size a quarter that of the attached application.
import java.util.Random;
public class HeapThrasher {
/* An object that takes up space on the heap -- no other purpose... */
private static class Stuff {
int one;
long two;
double three;
Object object = null;
}
/*
* An object that holds references on the heap
* and tracks how many have been allocated/freed
* using static members.
*/
private static class LotsOfStuff {
Stuff[] lotsOfStuff = null;
int allocated = 0;
int freed = 0;
public LotsOfStuff (int count) {
lotsOfStuff = new Stuff[count];
}
/* always called under lock */
private void update() {
if (allocated >= 1000) {
allocated(allocated);
allocated = 0;
}
if (freed >= 1000) {
freed(freed);
freed = 0;
}
}
/* stuffs an object in -- potentially freeing another */
public synchronized void add(int index) {
if (lotsOfStuff[index] != null) {
freed++;
}
lotsOfStuff[index] = new Stuff();
allocated++;
update();
}
/* potentially frees an object */
public synchronized void remove(int index) {
if (lotsOfStuff[index] != null) {
freed++;
lotsOfStuff[index] = null;
update();
}
}
}
/* A thread that randomly allocates objects */
private static class AllocateStuff implements Runnable {
int max = 0;
Random random = null;
public AllocateStuff(long seed, int max) {
random = new Random(seed);
this.max = max;
}
public void run() {
while (true) {
int i = random.nextInt(muchStuff.length);
int j = random.nextInt(max);
muchStuff[i].add(j);
}
}
}
/* A thread that randomly clears referrers to objects */
private static class FreeStuff implements Runnable {
int max = 0;
Random random = null;
public FreeStuff(long seed, int max) {
random = new Random(seed);
this.max = max;
}
public void run() {
while (true) {
int i = random.nextInt(muchStuff.length);
int j = random.nextInt(max);
muchStuff[i].remove(j);
}
}
}
/* The 'base' of all allocations -- sized in main method below */
private static LotsOfStuff[] muchStuff;
/* A rough count of how many objects have been allocated */
private static double totalAllocated = 0;
/* A rough count of how many object have been freed */
private static double totalFreed = 0;
/* accessor under lock for incrementing the totalAllocated member */
public static synchronized void allocated(int count) {
totalAllocated += count;
}
/* accessor under lock for incrementing the totalFreed member */
public static synchronized void freed(int count) {
totalFreed += count;
}
/*
* Runs fine with initial/max heap size set to 512M.
* Adjust the counters if you scale the heap.
*/
public static void main(String [] args) {
int numRoots = 500;
int numLeaves = 10000;
int numThreads = 100;
long seed = 42;
muchStuff = new LotsOfStuff[numRoots];
for (int i = 0; i < muchStuff .length; i++) {
muchStuff [i] = new LotsOfStuff(numLeaves);
for (int j = 0; j < numLeaves; j++) {
/* populate everything to begin with */
muchStuff [i].add(j);
}
}
Random random = new Random(seed);
for (int i = 0; i < numThreads; i++) {
Thread t = new Thread(new AllocateStuff(
random.nextLong(), numLeaves));
t.start();
t = new Thread(new FreeStuff(
random.nextLong(), numLeaves));
t.start();
}
Runtime runtime = Runtime.getRuntime();
while (true) {
/* race on totalAllocated/Freed but I don't care... */
StringBuffer buff = new StringBuffer();
buff.append("Allcoated: ");
buff.append(totalAllocated);
buff.append("\nFreed: ");
buff.append(totalFreed);
buff.append("\nTotal Memory: ");
buff.append(runtime.totalMemory());
buff.append("\nFree Memory: ");
buff.append(runtime.freeMemory());
System.out.println(new java.util.Date().toString());
System.out.println(buff.toString());
try {
Thread.currentThread().sleep(15000);
} catch (Exception eIgnore) {}
}
}
}
(Incident Review ID: 192388)
======================================================================
- duplicates
-
JDK-4896397 HPROF: heap=dump slow on Solaris, remain unresponsive (HeapThrasher bug)
-
- Closed
-