Tag Archives: memory

Memory conscious toList collector with Java 8

The following code:

    List source = Arrays.asList(1, 2, 3, 4, 5);
    List result = new ArrayList<> (source.size());
    for (int i : source) result.add(2 * i);

can easily be transformed to use lambdas in Java 8, for example:

    List result = source.stream()
                                 .map(i -> 2 * i)
                                 .collect(toList());

There is however an important difference: in the first code, the result list has a capacity of 5, whereas in the second code it has a capacity of 10, hence using more memory*.

The Collectors utility class also offers a toCollection method which accepts a Supplier<Collection>. That method allows to use a properly sized collection, for example:

    Supplier s = () -> new ArrayList<>(source.size());
    List result = source.stream()
                                 .map(i -> 2 * i)
                                 .collect(toCollection(s));

The code is now equivalent to the original code without lambdas and the result list has an optimal capacity.


*The capacity of the backing array can easily be checked with:

    Field f = ArrayList.class.getDeclaredField("elementData");
    f.setAccessible(true);
    System.out.println(((Object[]) f.get(result)).length);


Tagged , , ,

Size of objects in Java

I need to optimise the size taken by some of my objects as there will be millions of them and a factor of 3 starts to make a difference on a standard desktop PC, especially if it is a 32 bit machine.

JVM parameters

To get (fairly) accurate results using Runtime.getRuntime().freeMemory() I have used the following JVM parameters (on hotspot 7u11 x64 – some parameters might not be available on other JVMs): -server -Xms2000m -Xmx2000m -verbose:gc -XX:-UseTLAB -XX:+UseCompressedOops. The various flags do the following:

  • -Xms and -Xmx are well known and respectiely define the initial and maximum heap size. I set them high (2GB) to prevent the garbage collector from running during the test.
  • -verbose:gc makes the JVM print to the console when a GC is run. That enables to visually control that no GC ran during the tests.
  • -XX:-UseTLAB asks the JVM not to allocate memory in chunks (which it otherwise does for efficiency). Turning that option off gives more accurate and stable results.
  • -XX:+UseCompressedOops asks the JVM to compress references from 8 to 4 bytes. Note that it is on by default on Hotspot 7 x64.

Tests

The objective of my test was to determine the size of various classes that I could use to store tick data. The issue being that some ticks come with extra information and some don’t. I was wondering whether I should declare all the possible fields and leave them null when not available, or have an array or EnumMap instead. The only constraint here being memory footprint.

There are a few interesting observations – they are obviously empirical and might vary depending on several factors, including architecture (32/64 bits), JVM, other?:

  • an int takes 4 bytes
  • BUT an empty class (no members) and a class that has an int take the same space in memory (16 bytes), due to memory alignement
  • a null reference takes 4 bytes (same as int, due to compressed oop), with the same alignement observation
  • whether members are instantiated when declared or via a constructor does not make a difference (4 bytes)
  • an Object takes 16 bytes in memory
  • Adding 6 null Strings to my class (from TickBasic to TickComplete) adds 24 bytes, i.e. a size increase of 50%
  • The natural design is better than trying to use a “lazy” data structure with EnumMap

The test code at the bottom is fairly extensive but not very complicated. The output is (64 bits machine):

Object: 16 bytes
Object array (empty): 4 bytes
Object array (full) - incremental size: 16 bytes
Object with 1 int: 16 bytes
Object with 2 ints: 24 bytes
Object with 3 ints: 24 bytes
Object with 1 long: 24 bytes
Object with 2 longs: 32 bytes
Object with 3 longs: 40 bytes
Object with 1 null Object: 16 bytes
Object with 2 null Objects: 24 bytes
Object with 3 null Objects: 24 bytes
Object with 1 allocated Object: 16 bytes
Object with 2 allocated Objects: 24 bytes
Object with 3 allocated Objects: 24 bytes
TickBasic: 56 bytes
TickComplete: 80 bytes
TickCompleteWithConstructor: 80 bytes
TickArray: 128 bytes
TickEnumMap: 112 bytes

Test Code

public class TestMemory {

    private static final int SIZE = 100_000;
    private static Runnable r;
    private static Object[] array;
    private static Object o;
    private static Object o1 = new Object();
    private static Object o2 = new Object();
    private static Object o3 = new Object();

    private static void test(Runnable r, String name, int numberOfObjects) {
        long mem = Runtime.getRuntime().freeMemory();
        r.run();
        System.out.println(name + ": " + (mem - Runtime.getRuntime().freeMemory()) / numberOfObjects + " bytes");
    }

    public static void main(String[] args) throws Exception {
        System.out.println(System.getProperty("java.vm.name"));
        DateTime date = new DateTime(); //for some reason the result gets biased if we don't initialise DateTime first

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new Object(); } };
        test(r, "Object", SIZE);

        r = new Runnable() { public void run() { array = new Object[SIZE]; } };
        test(r, "Object array (empty)", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) array[i] = new Object(); } };
        test(r, "Object array (full) - incremental size", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith1Int(); } };
        test(r, "Object with 1 int", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith2Ints(); } };
        test(r, "Object with 2 ints", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith3Ints(); } };
        test(r, "Object with 3 ints", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith1Long(); } };
        test(r, "Object with 1 long", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith2Longs(); } };
        test(r, "Object with 2 longs", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith3Longs(); } };
        test(r, "Object with 3 longs", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith1NullObject(); } };
        test(r, "Object with 1 null Object", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith2NullObjects(); } };
        test(r, "Object with 2 null Objects", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith3NullObjects(); } };
        test(r, "Object with 3 null Objects", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith1Object(); } };
        test(r, "Object with 1 allocated Object", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith2Objects(); } };
        test(r, "Object with 2 allocated Objects", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith3Objects(); } };
        test(r, "Object with 3 allocated Objects", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new TickBasic(); } };
        test(r, "TickBasic", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new TickComplete(); } };
        test(r, "TickComplete", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new TickCompleteWithConstructor(new DateTime(), TickType.TRADE); } };
        test(r, "TickCompleteWithConstructor", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new TickArray(); } };
        test(r, "TickArray", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new TickEnumMap(); } };
        test(r, "TickEnumMap", SIZE);
    }

    public static class ObjectWith1Int  { int i; }
    public static class ObjectWith2Ints { int i, j; }
    public static class ObjectWith3Ints { int i, j, k; }
    public static class ObjectWith1Long  { long i; }
    public static class ObjectWith2Longs { long i, j; }
    public static class ObjectWith3Longs { long i, j, k; }
    public static class ObjectWith1NullObject  { Object o; }
    public static class ObjectWith2NullObjects { Object o, p; }
    public static class ObjectWith3NullObjects { Object o, p, q; }
    public static class ObjectWith1Object  { Object o = o1; }
    public static class ObjectWith2Objects { Object o = o1; Object p = o2; }
    public static class ObjectWith3Objects { Object o = o1; Object p = o2; Object q = o3; }

    public static class TickBasic {
        DateTime time = new DateTime();
        TickType type = TickType.TRADE;
        double value;
        int size;
    }

    public static class TickComplete {
        DateTime time = new DateTime();
        TickType type = TickType.TRADE;
        double value;
        int size;
        String cc;
        String ec;
        String mc;
        String bbc;
        String bsc;
        String rc;
    }

    public static class TickCompleteWithConstructor {
        DateTime time;
        TickType type;
        double value;
        int size;
        String cc;
        String ec;
        String mc;
        String bbc;
        String bsc;
        String rc;

        public TickCompleteWithConstructor(DateTime time, TickType type) {
            this.time = time;
            this.type = type;
        }
    }

    public static class TickArray {
        Object[] o = new Object[TickFields.values().length];
    }

    public static class TickEnumMap {
        Map values = new EnumMap(TickFields.class);
    }

    public static enum TickFields {

        TIME("time"),
        TYPE("type"),
        VALUE("value"),
        SIZE("size"),
        CONDITION_CODE("conditionCode"),
        EXCHANGE_CODE("exchangeCode"),
        MIC_CODE("micCode"),
        BROKER_BUY_CODE("brokerBuyCode"),
        BROKER_SELL_CODE("brokerSellCode"),
        RPS_CODE("rpsCode");
        private String code;

        TickFields(String code) {
            this.code = code;
        }
    }

    public static enum TickType {

        TRADE,
        BID,
        MID,
        ASK;
    }
}
Tagged , , ,