summaryrefslogtreecommitdiffstats
path: root/vespajlib/src/main/java/com/yahoo/cache/SizeCalculator.java
blob: e062ad8783f20e6946722c80e2e7db719bce8db1 (plain) (blame)
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.cache;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

/**
 * Size calculator for objects.
 * Thread safe.
 * @author vegardh
 * @see <a href="http://www.javaspecialists.co.za/archive/Issue078.html">MemoryCounter by Dr H M Kabutz</a>
 */
public class SizeCalculator {

    private static class ObjectSet {
        private final Map<Object, Object> map = new IdentityHashMap<>();

        public boolean had(Object obj) {
            if (map.containsKey(obj)) {
                return true;
            }
            map.put(obj, null);
            return false;
        }
    }

    private int getPointerSize() {
        return 4;
    }

    private int getClassSize() {
        return 8;
    }

    private int getArraySize() {
        return 16;
    }

    @SuppressWarnings("serial")
    private final IdentityHashMap<Class<?>, Integer> primitiveSizes = new IdentityHashMap<Class<?>, Integer>() {
        {
            put(boolean.class, 1);
            put(byte.class, 1);
            put(char.class, 2);
            put(short.class, 2);
            put(int.class, 4);
            put(float.class, 4);
            put(double.class, 8);
            put(long.class, 8);
        }
    };

    // Only called on un-visited objects and only with array.
    private long sizeOfArray(Object a, ObjectSet visitedObjects) {
        long sum = getArraySize();
        int length = Array.getLength(a);
        if (length == 0) {
            return sum;
        }
        Class<?> elementClass = a.getClass().getComponentType();
        if (elementClass.isPrimitive()) {
            sum += length * (primitiveSizes.get(elementClass));
            return sum;
        } else {
            for (int i = 0; i < length; i++) {
                Object val = Array.get(a, i);
                sum += getPointerSize();
                sum += sizeOfObject(val, visitedObjects);
            }
            return sum;
        }
    }

    private long getSumOfFields(Class<?> clas, Object obj,
            ObjectSet visitedObjects) {
        long sum = 0;
        Field[] fields = clas.getDeclaredFields();
        for (Field field : fields) {
            if (!Modifier.isStatic(field.getModifiers())) {
                if (field.getType().isPrimitive()) {
                    sum += primitiveSizes.get(field.getType());
                } else {
                    sum += getPointerSize();
                    field.setAccessible(true);
                    try {
                        sum += sizeOfObject(field.get(obj), visitedObjects);
                    } catch (IllegalArgumentException | IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
        return sum;
    }

    // Skip literal strings
    private boolean isIntern(Object obj) {
        if (obj instanceof String) {
            if (obj == ((String) obj).intern()) {
                return true;
            }
        }
        return false;
    }

    // Only called on non-visited non-arrays.
    private long sizeOfNonArray(Class<?> clas, Object obj,
            ObjectSet visitedObjects) {
        if (isIntern(obj)) {
            return 0;
        }
        long sum = getClassSize();
        while (clas != null) {
            sum += getSumOfFields(clas, obj, visitedObjects);
            clas = clas.getSuperclass();
        }
        return sum;
    }

    private long sizeOfObject(Object obj, ObjectSet visitedObjects) {
        if (obj == null) {
            return 0;
        }
        if (visitedObjects.had(obj)) {
            return 0;
        }
        Class<?> clas = obj.getClass();
        if (clas.isArray()) {
            return sizeOfArray(obj, visitedObjects);
        }
        return sizeOfNonArray(clas, obj, visitedObjects);
    }

    /**
     * Returns the heap size of an object/array
     *
     * @return Number of bytes for object, approximately
     */
    public long sizeOf(Object value) {
        ObjectSet visitedObjects = new ObjectSet();
        return sizeOfObject(value, visitedObjects);
    }

    /**
     * Returns the heap size of two objects/arrays, common objects counted only
     * once
     *
     * @return Number of bytes for objects, approximately
     */
    public long sizeOf(Object value1, Object value2) {
        ObjectSet visitedObjects = new ObjectSet();
        return sizeOfObject(value1, visitedObjects)
                + sizeOfObject(value2, visitedObjects);
    }

    /**
     * The approximate size in bytes for a list of objects, viewed as a closure,
     * ie. common objects are counted only once.
     *
     * @return total number of bytes
     */
    public long sizeOf(List<?> objects) {
        ObjectSet visitedObjects = new ObjectSet();
        long sum = 0;
        for (Object o : objects) {
            sum += sizeOfObject(o, visitedObjects);
        }
        return sum;
    }

}