Learning The "J"

Home » JAVA Learning » JAVA Core

Category Archives: JAVA Core

Java Objects Memory Structure

Memory layout of classes that have no instance attributes

In the JVM, every object (except arrays) has a 2 words header. The first word contains the object’s identity hash code plus some flags like lock state and age, and the second word contains a reference to the object’s class. Also, any object is aligned to an 8 bytes granularity. This is the first rule or objects memory layout:

Rule 1: every object is aligned to an 8 bytes granularity.

Now we know that if we call new Object(), we will be using 8 bytes of the heap for the two header words and nothing else, since the Object class doesn’t have any fields.

Memory layout of classes that extend Object

After the 8 bytes of header, the class attributes follow. Attributes are always aligned in memory to their size. For instance, ints are aligned to a 4 byte granularity, and longs are aligned to an 8 byte granularity. There is a performance reason to do it this way: usually the cost to read a 4 bytes word from memory into a 4 bytes register of the processor is much cheaper if the word is aligned to a 4 bytes granularity.

In order to save some memory, the Sun VM doesn’t lay out object’s attributes in the same order they are declared. Instead, the attributes are organized in memory in the following order:

  1. doubles and longs
  2. ints and floats
  3. shorts and chars
  4. booleans and bytes
  5. references

This scheme allows for a good optimization of memory usage. For example, imagine you declared the following class:

class MyClass {
    byte a;
    int c;
    boolean d;
    long e;
    Object f;        
}

If the JVM didn’t reorder the attributes, the object memory layout would be like this:

[HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[c:       4 bytes] 16
[d:       1 byte ] 17
[padding: 7 bytes] 24
[e:       8 bytes] 32
[f:       4 bytes] 36
[padding: 4 bytes] 40

Notice that 14 bytes would have been wasted with padding and the object would use 40 bytes of memory. By reordering the objects using the rules above, the in memory structure of the object becomes:

[HEADER: 8 bytes] 8

[e:       8 bytes] 16
[c:       4 bytes] 20
[a:       1 byte ] 21
[d:       1 byte ] 22
[padding: 2 bytes] 24
[f:       4 bytes] 28
[padding: 4 bytes] 32

This time, only 6 bytes are used for padding and the object uses only 32 bytes of memory.

So here is rule 2 of object memory layout:

Rule 2: class attributes are ordered like this: first longs and doubles; then ints and floats; then chars and shorts; then bytes and booleans, and last the references. The attributes are aligned to their own granularity.

Now we know how to calculate the memory used by any instance of a class that extends Object directly. One practical example is the java.lang.Boolean class. Here is its memory layout:

[HEADER:  8 bytes]  8 
[value:   1 byte ]  9
[padding: 7 bytes] 16

An instance of the Boolean class takes 16 bytes of memory!  (Notice the padding at the end to align the object size to an 8 bytes granularity.)

Memory layout of subclasses of other classes

The next three rules are followed by the JVM to organize the the fields of classes that have superclasses. Rule 3 of object memory layout is the following:

Rule 3: Fields that belong to different classes of the hierarchy are NEVER mixed up together. Fields of the superclass come first, obeying rule 2, followed by the fields of the subclass.

Here is an example:

class A {
   long a;
   int b;
   int c;
}

class B extends A {
   long d;
}

An instance of B looks like this in memory:

[HEADER:  8 bytes]  8
[a:       8 bytes] 16
[b:       4 bytes] 20
[c:       4 bytes] 24
[d:       8 bytes] 32

The next rule is used when the fields of the superclass don’t fit in a 4 bytes granularity. Here is what it says:

Rule 4: Between the last field of the superclass and the first field of the subclass there must be padding to align to a 4 bytes boundary.

Here is an example:

class A {
   byte a;
}

class B {
   byte b;
}
[HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[b:       1 byte ] 13
[padding: 3 bytes] 16

Notice the 3 bytes padding after field a to align b to a 4 bytes granularity. That space is lost and cannot be used by fields of class B.

The final rule is applied to save some space when the first field of the subclass is a long or double and the parent class doesn’t end in an 8 bytes boundary.

Rule 5: When the first field of a subclass is a double or long and the superclass doesn’t align to an 8 bytes boundary, JVM will break rule 2 and try to put an int, then shorts, then bytes, and then references at the beginning of the space reserved to the subclass until it fills the gap.

Here is an example:

class A {
  byte a;
}

class B {
  long b;
  short c;  
  byte d;
}

Here is the memory layout:

[HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[c:       2 bytes] 14
[d:       1 byte ] 15
[padding: 1 byte ] 16
[b:       8 bytes] 24

At byte 12, which is where class A ‘ends’, the JVM broke rule 2 and stuck a short and a byte before a long, to save 3 out of 4 bytes that would otherwise have been wasted.

Memory layout of arrays

Arrays have an extra header field that contain the value of the ‘length’ variable. The array elements follow, and the arrays, as any regular objects, are also aligned to an 8 bytes boundary.

Here is the layout of a byte array with 3 elements:

[HEADER:  12 bytes] 12
[[0]:      1 byte ] 13
[[1]:      1 byte ] 14
[[2]:      1 byte ] 15
[padding:  1 byte ] 16

And here is the layout of a long array with 3 elements:

[HEADER:  12 bytes] 12
[padding:  4 bytes] 16
[[0]:      8 bytes] 24
[[1]:      8 bytes] 32
[[2]:      8 bytes] 40

Memory layout of inner classes

Non-static inner classes have an extra ‘hidden’ field that holds a reference to the outer class. This field is a regular reference and it follows the rule of the in memory layout of references. Inner classes, for this reason, have an extra 4 bytes cost.

Final thoughts

We have learned how to calculate the shallow size of any Java object in the 32 bit Sun JVM. Knowing how memory is structured can help you understand how much memory is used by instances of your classes.

Using Assertion in Java

We can use assertion to test our assumption about programs. That means it validates our program!

In another words we can say that assertions ensures the program validity by catching exceptions and logical errors. They can be stated as comments to guide the programmer. Assertions are of two types:

1) Preconditions
2) Postconditions.

Preconditions are the assertions which invokes when a method is invoked and Postconditions are the assertions which invokes after a method finishes.

Where to use Assertions

We can use assertions in java to make it more understanding and user friendly, because assertions can be used while defining preconditions and post conditions of the program. Apart from this we can use assertions on internal, control flow and class invariants as well? to improve the programming experience.
Declaring Assertion:

Assertion statements have two form-

assert expression;
This statement evaluates expression and throws an AssertionError if the expression is false.
assert expression1 : expression2
This statement evaluates expression1 and throws an AssertionError with expression2 as the error message if expression1 is false.
Now lets see two examples of each which explains you more clearly.

public class Foo {

    public String testAssert(Integer id) {
        assert id != null && id > 0;
        String result = "BLANK";     
        if (id > 50) {
            result = "IF-- " + id;
        } else {
            result = "ELSE-- " + id;
        }
        assert result != null;
       
        return result;
    }
    
    public static void main(String ar[]){
        Foo f=new Foo();
        System.out.println(f.testAssert(0));
    }
}
import java.util.Scanner;

public class Foo {

    public static void main(String args[]) {
        Scanner scanner = new Scanner(System.in);

        System.out.print("Enter a number between 0 and 20: ");
        int value = scanner.nextInt();
        assert (value >= 0 && value <= 20) :
                "Invalid number: " + value;
        System.out.printf("You have entered %d\n", value);
    }
}

To run the above example,
Compile the example with: javac Foo.java
Run the example with: java -ea Foo
To enable assertions at runtime, -ea command-line option is used

ZIP/UnZip files using Java

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

public class ZipUtil {

    public static void zipDir(String dir2zip, ZipOutputStream zos) {
        try {
            File zipDir = new File(dir2zip);
            String[] dirList = zipDir.list();
            byte[] readBuffer = new byte[2156];
            int bytesIn = 0;

            for (int i = 0; i < dirList.length; i++) {
                File f = new File(zipDir, dirList[i]);
                if (f.isDirectory()) {
                    String filePath = f.getPath();
                    zipDir(filePath, zos);
                    continue;
                }
                FileInputStream fis = new FileInputStream(f);
                ZipEntry anEntry = new ZipEntry(f.getPath());
                zos.putNextEntry(anEntry);

                while ((bytesIn = fis.read(readBuffer)) != -1) {
                    zos.write(readBuffer, 0, bytesIn);
                }
                fis.close();
            }
        } catch (Exception e) {
        }
    }

    public static void unZipDir(String source, String destDir) {
        try {
//                        String sourceZipFile="E:\\curDir.zip";
//                        String destFolder="E:\\TEST\\";

            String sourceZipFile = source;
            String destFolder = destDir;
            ZipFile zipFile = new ZipFile(sourceZipFile);
            Enumeration<?> enu = zipFile.entries();
            while (enu.hasMoreElements()) {
                ZipEntry zipEntry = (ZipEntry) enu.nextElement();

                String name = zipEntry.getName();
                //System.out.println("NAME   " + name);
                long size = zipEntry.getSize();
                long compressedSize = zipEntry.getCompressedSize();
                System.out.printf("name: %-20s | size: %6d | compressed size: %6d\n",
                        name, size, compressedSize);

                File file = new File(destFolder + name.substring(3, name.length()));
                if (name.endsWith("/")) {
                    file.mkdirs();
                    continue;
                }

                File parent = file.getParentFile();
                if (parent != null) {
                    parent.mkdirs();
                }

                InputStream is = zipFile.getInputStream(zipEntry);
                FileOutputStream fos = new FileOutputStream(file);
                byte[] bytes = new byte[1024];
                int length;
                while ((length = is.read(bytes)) >= 0) {
                    fos.write(bytes, 0, length);
                }
                is.close();
                fos.close();
            }
            zipFile.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String ar[]) {
        try {            

			//Zipping files
			//ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("E:\\curDir.zip"));
			//zipDir("D:\\CRRTAGS", zos);
			//zos.close();

			//UnZipping files
            	unZipDir("E:\\curDir.zip", "E:\\TEST\\");            
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}