Value Types or: Let’s add another type system to Java

Java has two major type systems, primitives types(eg. int, byte, etc) and class/object types(eg. java.lang.String). Primitive types provide concise storage of data with no attach methods, while class types provide the ability to extend the functionality of an object while sacrificing space. These type systems are on different ends of the space/performance spectrum and Value Types aim to get the best of both worlds.

Value Types are a JDK Enhancement Proposals(JEP) that will add a change to the JVM instruction set allowing “support [for] small immutable, identityless value types”. The “identityless” notion is the big difference here. In Java, the identity is there to support mutability, synchronization, and few other features. The identity comes with a cost to performance and increases the object’s footprint.

There are a number of features that could be gained from Value Types. Tuples (esp. nice when trying to return multiple value from a method), numeric (supporting more than the eight default Java primitives), native types (ie taking advantage of specific processor types), algebraic data types (see jADT for some good examples/rational) and iterator/cursor simplification.

Below is some examples showing two of the possible gains from value types: Reduction in footprint, and increase in performance.

Performance – Reductions in Opcodes

Take a look at this simple example.

public class Example {
    public static void main(String[] args) {
        Example example = new Example();
        int intPrimitive = example.intMethod(1, 2);
        Integer intObject = example.integerMethod(1, 2);
    }
    public int intMethod(int a, int b) {
        return a + b;
    }
    public Integer integerMethod(Integer a, Integer b) {
        return a + b;
    }
}

The code is adding the same two numbers together. The first time using the primitive int type and the second time using the Integer object. Lets first take a look at the byte code for the primitive method.

  public int intMethod(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: iload_2
         2: iadd
         3: ireturn

Pretty straight forward. The method loads the value 1 (iload_1), then loads the value 2 (iload_2) and returns them after adding.
And now, the Integer version.

  public java.lang.Integer integerMethod(java.lang.Integer, java.lang.Integer);
    descriptor: (Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: aload_1
         1: invokevirtual #10   // Method java/lang/Integer.intValue:()I
         4: aload_2
         5: invokevirtual #10   // Method java/lang/Integer.intValue:()I
         8: iadd
         9: invokestatic  #5    // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        12: areturn

It takes three more commands and the bytecode array is 4x larger. What is killing us here is the un/boxing of the Integer object. (Note: In this code we’re not using any Object features.) This is pretty basic knowledge but it’s neat to see in the bytecode.

Footprint – Header Size

Let skip past the size of an int (4-bytes) and go straight to Integer. Using jol–a tool to help analyze object layout–we can see the internals of a  workings object.

Running 64-bit HotSpot VM.
java.lang.Integer object internals:
 OFFSET  SIZE  TYPE DESCRIPTION                    VALUE
      0    12       (object header)                N/A
     12     4   int Integer.value                  N/A
Instance size: 16 bytes (estimated, the sample instance is not available)
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

Notice that we waste 12-bytes on the header. That means that an Integer is 4 times the size of an int.

These little excesses add up after a while. In fact, this is how some algorithm classes make sure that when you do the programming assignments that you actually implement the algorithm and don’t use a library (or several libraries) to bypass learning how an algorithm is implemented. Think about it, if you have to sort an array of 100 ints with bubble sort and you instead use Collection.sort() then your program is going to be much larger than if you used the primitive int, not to mention slower.


Most of my understanding for this post comes from this blog and from the JEP description, which is long but interesting.