Lesson Learnt : Basic Java Memory Consumption for In-memory Cache.

I know that these days we’re in the era of cheap memory. You can pretty much get 8GB of memory on a Notebook off the shelf. I can still remember in the past when I was playing with my 486DX with 64Mb or memory and it was too fast! yeah. People might question what am I doing using a lot of memory and why would you care. In my case, I’m building a cache which I have to pretty much hold every single object inside the memory. I have over 1 million objects. Well it’s just 1 million objects which it’s not a big deal but I have to index and make it searchable which I was told to use hibernate search. Hiberate search is pretty much a wrapper on top of Lucene. That’s why my my cache it’s very memory consuming. So, I have to be very careful what I put in my domain object. The example I’m using it’s just a basic example which in the real project there will be much more than just this.

For example, I have one Store object which it has a method to return jsonified string. So, what I did I just passed ObjectMapper to transform my object to be json. However, what I didn’t realise was that if I passed that from the constructor my object has to store ObjectMapper inside the object as well and ObjectMapper is a really beefy object. So, If I created a million objects I have to hold another million objects of ObjectMapper as well. So, I just had to pass ObjectMapper just when I want to use it. And that save me a lot of memory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package com.noppanit;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;

import java.io.Serializable;
import java.util.HashMap;

import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;

public class TestMemoryConsumption {
    private static final long MEGABYTE = 1024L * 1024L;
    public static final int LOOP = 1000000;

    public static long bytesToMegabytes(long bytes) {
        return bytes / MEGABYTE;
    }

    @Test
    public void testMemoryConsumption() throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();
        HashMap<String, Object> map = new HashMap<String, Object>();
        for (int i = 0; i < LOOP; i++) {
            StoreWithToJson storeWithToJson = new StoreWithToJson("name", "description", objectMapper);
            map.put(String.valueOf(i), storeWithToJson);
        }

        printMemoryConsumption();

    }

    @Test
    public void testMemoryConsumptionWithoutObjectMapper() throws Exception {
        HashMap<String, Object> map = new HashMap<String, Object>();
        for (int i = 0; i < LOOP; i++) {
            Store store = new Store("name", "description");
            map.put(String.valueOf(i), store);
        }

        printMemoryConsumption();

    }

    private void printMemoryConsumption() {
        Runtime runtime = Runtime.getRuntime();
        runtime.gc();
        System.runFinalization();
        long memory = runtime.totalMemory() - runtime.freeMemory();
        System.out.println("Used memory is bytes: " + memory);
        System.out.println("Used memory is megabytes: "
                + bytesToMegabytes(memory));
    }
}

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = ANY, setterVisibility = NONE)
class StoreWithToJson implements Serializable {
    private String name;
    private String description;
    private ObjectMapper mapper;

    public StoreWithToJson(String name, String description, ObjectMapper mapper) {
        this.name = name;
        this.description = description;
        this.mapper = mapper;
    }

    public String toJson() throws JsonProcessingException {
        return mapper.writeValueAsString(this);
    }

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }
}


class Store implements Serializable {
    private String name;
    private String description;

    public Store(String name, String description) {
        this.name = name;
        this.description = description;
    }

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }
}

Result from passing ObjectMapper from the constructor.

1
2
Used memory is bytes: 3723968
Used memory is megabytes: 3

Result from passing ObjectMapper just when I want to use it.

1
2
Used memory is bytes: 1928536
Used memory is megabytes: 1

I know this might be obvious for some people but if you have the same problems as myself this might help shed some lights to something else.

Dec 5th, 2013

Comments