Name: ap32198 Date: 10/28/98
We've built a large Java app, with over 2000
classes. Whenever the Java garbage collector
runs it pages in not only the entire Java heap
(presumably due to the mark and sweep) but also
many other pages, which seem to be part of the
space allocated by the JVM for classes.
This effect is very significant for our app.
Our Java heap is around 5-6MB and we GC often,
so we'd expect our working set to start at
around 6MB, because of the GC paging in all
the Java heap. But in fact it is around 12MB
because the GC touches all the classes as
well (probably the constant pools?). This is
just too big and, together with the working
set of our native code, means our app won't
run on our target platform.
If the portions of the classes needed by the
GC were loaded in a contiguous area I suspect
our working set would be several megabytes
lower, which would really help. Java's high
working set is a big problem for us, and I
suspect it will also bite others who are
writing large Java apps - particularly as
the Java libraries grow bigger.
I have test programs that generate many dummy
classes and then load them to see their effect
on working set. These tests confirm that loading
more classes, even without creating more Java
objects, increases working set substantially.
If the dummy classes are larger than a page the
effect is more marked, presumably because you
then drag in at least a page per class during
the garbage collection.
I'm enclosing the programs, with instructions,
though they're going to get horribly wrapped
in this little text field
Thank you for looking at this!
Trevor Morris
Here's a description of the test programs, followed by the
programs themselves:
- BogusClassGenerator.java. This generates a number of Java files,
BogusClass0.java, BogusClass1.java etc. Each Java file contains
a very simple class with just a constructor; the constructor
contains "int i = 0;" and then an arbitrary number of "i += 1000;"
assignments. BogusClassGenerator takes arguments which control
the number of classes generated, plus the number of assignment
lines (just a way to make the class file bigger by adding more
bytecode). e.g:
java BogusClassGenerator -l 500 1000
generates 1000 bogus classes, each containing 500 assignments
- SimpleThrasher.java. This loads some number of the BogusClasses
generated by BogusClassGenerator, and stuffs them in an array. It
also allocates some number of filler objects, just to get something
on the Java heap (number of classes and number of filler objects
can be controlled by arguments). It then goes into an infinite
loop where it sleeps, does a GC, sleeps, flushes working set and
then repeats (you'll need to add the flush working set call yourself,
if you're doing this on Solaris - if not I can send you the NT API
to do it). Watching the program with a working set monitor, you'll
see a plain up and down function - working set drops when flushed
then jumps on the GC, then drops and so on. Example of running:
java -verbosegc SimpleThrasher 1000
Loads 1000 bogus classes (and, by default, creates 1000 3k filler
objects).
I did some experiments on my NT box with these two programs and the
results were pretty interesting:
NO CLASSES:
Running SimpleThrasher, but loading no bogus classes. 1000 3k filler objects.
Java Heap: 303728/3554504 i.e. 3250776, about 90% utilization
Working Set after GC: 4251648
SMALL CLASS:
Running SimpleThrasher, loading 1000 bogus classes, each with zero lines
of assignments (class files ~250 bytes) + 1000 3k filler objects.
Java Heap: 535352/3882184 i.e. 3346832, about 85% utilization
Working Set after GC: 5038080 (786432 bytes greater than base case)
BIG CLASS:
Running SimpleThrasher, loading 1000 bogus classes, each with 50 lines
of assignments (class files ~5260 bytes) + 1000 3k filler objects.
Java Heap: 535352/3882184 i.e. 3346832, about 85% utilization (no change
from small class case)
Working Set after GC: 8560640 (4308992 bytes greater than base case,
3522560 greater than small class case)
Note, page size on NT is 4k so probably around 16 of the small classes
could fit on a page (assuming the VM data structure is a similar size
to the class file). The big classes are probably bigger than a page.
So, bottom line, moving to bigger classes (but the same number of classes)
increased the working set by around 3.3MB, presumably because the GC
visits every class and now has to page in at least a page per bogus
class, instead of (on average) around 1/16th of a page in the small
class case.
START FILE BogusClassGenerator.java:
import java.io.FileOutputStream;
import java.io.BufferedOutputStream;
import java.io.PrintWriter;
import java.io.IOException;
public class BogusClassGenerator {
/** Maximum number of bogus classes we generate */
public static final int MAX_BOGUS_CLASSES = 5000;
/** Base name for bogus classes */
public static final String BOGUS_CLASS_BASE_NAME = "BogusClass";
static void generateClass(int classNumber, int lineCount) {
String className = BOGUS_CLASS_BASE_NAME + classNumber;
try {
FileOutputStream fileStream =
new FileOutputStream(className + ".java");
PrintWriter outStream =
new PrintWriter(new BufferedOutputStream(fileStream));
outStream.println("public class " + className + " {");
outStream.println(" " + className + "() {");
outStream.println(" int i = 0;");
for (int i = 0; i < lineCount; i++) {
outStream.println(" i += 1000;");
}
outStream.println(" }");
outStream.println("}");
outStream.close();
fileStream.close();
}
catch (IOException e) {
System.out.println("Failed to generate class " + className +
" due to IO exception: " + e);
System.exit(1);
}
}
static private void usage() {
System.out.println("Usage: java BogusClassGenerator -l lines count");
System.out.println("-l lines : number of lines of code to put in BogusClass constructor");
System.out.println("count : number of BogusClass<i> classes to generate");
System.exit(1);
}
public static void main(String[] args) {
int classCount = 0;
int lineCount = 0;
int i = 0;
while (i < args.length) {
String arg = args[i++];
if (arg.startsWith("-")) {
if (arg.equals("-l")) {
if (i < args.length) {
String lineCountString = args[i++];
try {
lineCount = Integer.parseInt(lineCountString, 10);
}
catch (NumberFormatException e) {
System.out.println("Invalid line count: " +
lineCountString);
System.exit(1);
}
}
else {
usage();
}
}
else {
usage();
}
}
else {
try {
classCount = Integer.parseInt(arg);
if (classCount > MAX_BOGUS_CLASSES) {
System.out.println("Too many classes - maximum is " +
MAX_BOGUS_CLASSES);
System.exit(1);
}
}
catch (NumberFormatException e) {
usage();
}
}
}
System.out.println("Generating " + classCount + " bogus classes" +
" each with " + lineCount +
" lines in their constructor");
for (i = 0; i < classCount; i++) {
generateClass(i, lineCount);
}
}
}
END FILE BogusClassGenerator.java
START FILE SimpleThrasher.java
public class SimpleThrasher {
/** Overhead for an object plus an array */
public static final int PER_OBJECT_OVERHEAD = 24;
/**
* Instance variable - used to make each SimpleThrasher object take
* up 3K.
*/
private byte[] mySpace = new byte[(3 * 1024) - PER_OBJECT_OVERHEAD];
/**
* Array which is always allocated and then is filled with either
* Objects or Class objects. This is an attempt to keep the Java
* heap roughly the same size no matter how many classes are loaded
*/
private static final Object[] TheClasses =
new Object[BogusClassGenerator.MAX_BOGUS_CLASSES];
SimpleThrasher() {
}
static void sleep(long millis) {
try {
Thread.currentThread().sleep(millis);
}
catch (InterruptedException e) {
System.out.println("SLEEP INTERRUPTED");
}
}
static private void usage() {
System.out.println("Usage: java SimpleThrasher -i interval <count>");
System.out.println("-f count : number of filler objects to create (each ~3k)");
System.out.println("-i interval: interval between gcs, in seconds");
System.out.println("count : number of BogusClass<i> classes to load");
System.exit(1);
}
public static void main(String[] args) {
int classCount = 0;
int fillerCount = 1000;
int interval = 3;
int i = 0;
while (i < args.length) {
String arg = args[i++];
if (arg.startsWith("-")) {
if (arg.equals("-f")) {
if (i < args.length) {
String fillerCountString = args[i++];
try {
fillerCount = Integer.parseInt(fillerCountString, 10);
}
catch (NumberFormatException e) {
System.out.println("Invalid filler count: " +
fillerCountString);
System.exit(1);
}
}
else {
usage();
}
}
else if (arg.equals("-i")) {
if (i < args.length) {
String intervalString = args[i++];
try {
interval = Integer.parseInt(intervalString, 10);
}
catch (NumberFormatException e) {
System.out.println("Invalid interval count: " +
intervalString);
System.exit(1);
}
}
else {
usage();
}
}
else {
usage();
}
}
else {
try {
classCount = Integer.parseInt(arg);
if (classCount > BogusClassGenerator.MAX_BOGUS_CLASSES) {
System.out.println("Too many classes - maximum is " +
BogusClassGenerator.MAX_BOGUS_CLASSES);
System.exit(1);
}
}
catch (NumberFormatException e) {
usage();
}
}
}
System.out.println("Loading " + classCount + " bogus classes");
String bogusClassName = BogusClassGenerator.BOGUS_CLASS_BASE_NAME;
for (i = 0; i < classCount; i++) {
try {
TheClasses[i] = Class.forName(bogusClassName + i);
}
catch (ClassNotFoundException e) {
System.out.println("Couldn't load " + bogusClassName + i +
": " + e);
System.exit(1);
}
}
System.out.println("Loaded " + classCount + " bogus classes");
for (i = classCount; i < BogusClassGenerator.MAX_BOGUS_CLASSES; i++) {
TheClasses[i] = new Object();
}
System.out.println("Creating " + fillerCount + " filler objects");
SimpleThrasher[] filler = new SimpleThrasher[fillerCount];
for (i = 0; i < fillerCount; i++) {
filler[i] = new SimpleThrasher();
}
System.out.println("Created " + fillerCount + " filler objects");
System.out.println("Starting thrasher; interval = " + interval);
for (;;) {
sleep(interval * 1000);
System.gc();
if (filler == null) {
System.out.println("ERROR, should never happen!");
}
sleep(interval * 1000);
ec.util.Native.flushWorkingSet();
}
}
}
END FILE SimpleThrasher.java
(Review ID: 26027)
======================================================================
- duplicates
-
JDK-4158880 JDK in-memory representation of classes hugely inefficient
-
- Closed
-