PersonalJavaTM 1.1 Memory Usage Technical Note

Abstract

One of the primary design goals of the PersonalJava Virtual Machine (VM) is the minimization of the static memory footprint and the runtime memory usage. The static memory footprint is comprised of mostly ROM, but also some RAM. It is the memory which is used when the PersonalJava platform classes are preloaded with JavaCodeCompact. The runtime memory usage, as the term suggests, is the memory which is consumed at runtime by the PersonalJava VM for dynamically loaded classes, native and JavaTM stacks, and heap storage to support dynamic allocation and garbage collection. Through Sun's work on the PersonalJava 1.1 VM, the following areas of memory usage have been decreased: the preloaded and dynamically loaded class memory footprint, the native stack usage and the Java stack usage. The result is a system with drastically reduced ROM and RAM requirements which has no perceivable degradation in execution speed.

This paper discusses in detail the optimizations which were made to achieve an approximately 42% decrease in class memory footprint from PersonalJava 1.0 to PersonalJava 1.1. It is assumed that the reader is familiar with Java and the workings of the Java Virtual Machine as specified in "The Java Virtual Machine Specification" by Lindholm and Yellin. Familiarity with a Sun Virtual Machine is also helpful.

 

Introduction

By conducting an extensive analysis of Java memory usage, a few categories from among the top memory consumers were identified as having the most potential for memory usage reduction:
  1. The memory footprint of classes and their various components
  2. Native stack usage
  3. Java stacks
  4. Java heap usage
  5. C code size
#1, #2 and #3 were selected as having the most potential for application-independent memory use reductions. The focus of Sun's work is on these three categories. #4 is application dependent and therefore hard to reduce generically. #5 does not lend itself to too much reduction without significant rewrites of the VM and native methods.

Class Footprint Reductions

It is customary for the classes of a Java program to be loaded as late during the program's execution as possible: they are loaded on demand from the network (stored on a server), or from a local file system when first referenced during the program's execution. The VM locates and loads each class, parses the class file, allocates internal data structures for its various components, and links it in with other loaded classes. This process makes the method code in the class readily executable by the VM. A large part of the runtime memory consumption in Java is due to the dynamic allocation of the internal data structures for classes and their components (class metadata), and of the memory allocated for the method bytecodes.

For small and embedded systems lacking fast dynamic class loading facilities such as a network connection, a local file system, or other permanent storage, a "class preloader" to load the class offline makes sense. PersonalJava has a class preloader called JavaCodeCompact which performs this task. JavaCodeCompact creates the internal VM data structures representing a class offline, and lays them out in mostly read-only memory.

Changes were made that reduce the memory allocations required per class. Some of these changes apply only to JavaCodeCompact, thus affecting only the ROM footprint. Some of the other techniques apply both to preloaded and dynamically loaded classes, affecting both the ROM and RAM usage.

 

Memory reductions that apply to preloaded classes only

Using JavaCodeCompact has the advantage of preloading all platform classes at once. This allows for the sharing of information between classes, thereby reducing the ROM requirement considerably. Space is saved by exploiting the fact that all symbolic references in these classes are resolved, and that bytecode instructions referring to these symbolic entries have been quickened (i.e. re-written with versions that do not do symbolic lookups).

Memory reductions that apply to both preloaded and dynamically loaded classes

Numbers

Here are some measurements that demonstrate the effectiveness of the class memory footprint reductions in the PersonalJava VM. We measure the memory footprint of pre-loaded platform classes in ROM and RAM. This measurement is also highly characteristic of memory footprint for dynamically loaded classes. In addition, we show how we have made Java classes more ROMable.

The first comparison is of PersonalJava 1.1 with all optional packages included (rmi, sql, math, zip, and code signing), with a similarly configured JDK 1.1.6, adjusted to exclude components unsupported by PersonalJava (e.g. java.security.acl).

 
PersonalJava 1.1 JDK 1.1.6
No of classes 913 911
Total size of class files 1.997M 2.022M
Total class memory footprint 1.593M 2.222M
% of RAM in total footprint 0.2% 6.5%

JDK 1.1.6 comes out to be around 39.5% larger. It also has about 32.5 times the RAM requirement of pjava 1.1.

The second comparison is of PersonalJava 1.1 with no optional packages included, with PersonalJava 1.0.

 
PersonalJava 1.0 PersonalJava 1.1
No of classes 641 689
Total size of class files 1.284M 1.506M
Total class memory footprint 1.402M 1.227M
% of RAM in total footprint 7.6% 0.3%

PersonalJava 1.0 comes out to be 14% bigger, even though it has only 83% of the .class data of PersonalJava 1.1. Normalized, this translates to PersonalJava 1.0 class footprint being 38% bigger than PersonalJava 1.1. Similarly, the RAM requirement for the classes has gone down as much as 25-fold when measured relative to total class memory footprint. The normalized value is around 30-fold.

Native Stack Size Reductions

Extensive static analysis of the native code in the VM and supporting libraries was performed with the aim of: These make it possible to determine a tight upper bound on the stack usage of any thread. To realize this, the following investigations and modifications were performed: Based on these efforts, a default thread stack size of 20k is expected to be a good candidate if libraries optimized to reduce stack consumption are used. Moreover, the potential for a memory corruption caused by stack overflow is eliminated since it is guaranteed that a StackOverflowError will be thrown in advance at a safe point. This result is a significant improvement from the PersonalJava 1.0 level of 128k, a very conservative, yet not even safe stack size choice.

Note that the Sparc architecture consumes more stack because of its register windows architecture, and that a lower default stack size can be expected for embedded purpose CPUs.

Java Stack Size Reductions

Java stacks in the PersonalJava VM are allocated in a chunky fashion during execution of a program; whenever the VM determines that more Java stack space is required, it allocates new stack chunks. A few typical applications were analyzed to figure out an optimal Java stack chunk size for PersonalJava with the goal of minimizing stack space waste and making the average case run in as few chunks of stack space as possible. It was determined that a stack chunk size of 2k is a good number, improving upon the 8k size in the PersonalJava 1.0 VM.