Contador de Memória para Java 1.4

Rafael Steil

Calcule a memória RAM que seus objetos utilizam.



The Java Specialists' Newsletter [Issue 078] - 2003-09-29
Author: Dr. Heinz M. Kabutz

Contador de Memória para Java 1.4

Antes de iniciar, preciso avisar que estou usando classes que funcionam apenas a partir do Java 1.4. Além disso, os tamanhos de memória foram alterados no JDK 1.4, portanto existem diferenças entre o 1.4 e o 1.3. Esta classe é apenas para dar uma idéia do tamanho de um objeto.

Depois da minha newsletter #29, recebi alguns e-mails dizendo que seria muito mais fácil serializar o objeto e então contar os bytes. Se eu quisesse saber o quanto de banda um objeto iria usar, esta abordagem faria sentido. Porém, minha abordagem é usada para medir memória RAM. Outra vantagem da minha abordagem é que o objeto não precisa ser serializável.

A primeira classe que temos é chamada MemorySizes e irá dizer à classe contadora de memória o quanto de memória cada elemento utiliza. Determinei isso empiricamente.


01 import java.util.*;
02 
03 public class MemorySizes {
04     private final Map primitiveSizes = new IdentityHashMap() { 
05         {
06             put(boolean.class, new Integer(1));
07             put(byte.class, new Integer(1));
08             put(char.class, new Integer(2));
09             put(short.class, new Integer(2));
10             put(int.class, new Integer(4));
11             put(float.class, new Integer(4));
12             put(double.class, new Integer(8));
13             put(long.class, new Integer(8));
14         }
15     };
16     
17     public int getPrimitiveFieldSize(Class clazz) {
18         return ((IntegerprimitiveSizes.get(clazz)).intValue();
19     }
20     
21     public int getPrimitiveArrayElementSize(Class clazz) {
22         return getPrimitiveFieldSize(clazz);
23     }
24     
25     public int getPointerSize() {
26         return 4;
27     }
28     
29     public int getClassSize() {
30         return 8;
31     }
32 }


Em seguida, temos a classe que conta o tamanho da memória. Para uma explicação deste código, sugiro que você consulte a newsletter original.


001 import java.lang.reflect.*;
002 import java.util.*;
003 
004 /**
005  * Esta classe pode estimar o quanto de memória um objeto utiliza. Ela é
006  * bastante precisa para o JDK 1.4.2.  É baseada na newsletter #29.
007  */
008 public final class MemoryCounter {
009     private static final MemorySizes sizes = new MemorySizes();
010     private final    Map    visited    = new IdentityHashMap();
011     private final    Stack stack    = new Stack();
012 
013     public synchronized long estimate(Object obj) {
014         assert visited.isEmpty();
015         assert stack.isEmpty();
016         long result    = _estimate(obj);
017         
018         while (!stack.isEmpty()) {
019             result +=    _estimate(stack.pop());
020         }
021 
022         visited.clear();
023         return result;
024     }
025 
026     private boolean skipObject(Object    obj) {
027         if (obj    instanceof String) {
028             // isto    não    causará    vazamento de memória já    que
029             // Strings internos não usados serão jogados fora
030             if (obj == ((Stringobj).intern()) {
031                 return true;
032             }
033         }
034         
035         return (obj    == null)|| visited.containsKey(obj);
036     }
037 
038     private long _estimate(Object    obj) {
039         if (skipObject(obj)) 
040             return    0;
041             
042         visited.put(obj, null);
043         long result    = 0;
044         Class clazz    = obj.getClass();
045     
046         if (clazz.isArray()) {
047             return _estimateArray(obj);
048         }
049         
050         while (clazz !=    null) {
051             Field[] fields = clazz.getDeclaredFields();
052     
053             for (int i = 0; i    < fields.length; i++) {
054                 if (!Modifier.isStatic(fields[i].getModifiers())) {
055                     if (fields[i].getType().isPrimitive()) {
056                         result += sizes.getPrimitiveFieldSize(fields[i].getType());
057                     }
058                     else {
059                         result += sizes.getPointerSize();
060                         fields[i].setAccessible(true);
061                         
062                         try    {
063                             Object toBeDone =    fields[i].get(obj);
064                             
065                             if (toBeDone != null) {
066                                 stack.add(toBeDone);
067                             }
068                         
069                         catch (IllegalAccessException ex) { 
070                             assert false;    
071                         }
072                     }
073                 }
074             }
075             
076             clazz    = clazz.getSuperclass();
077         }
078         
079         result += sizes.getClassSize();
080         return roundUpToNearestEightBytes(result);
081     }
082 
083     private long roundUpToNearestEightBytes(long result) {
084         if ((result    % 8!=    0) {
085             result +=    -    (result    % 8);
086         }
087         
088         return result;
089     }
090 
091     protected    long _estimateArray(Object obj)    {
092         long result    = 16;
093         int    length = Array.getLength(obj);
094             
095         if (length != 0) {
096             ClassarrayElementClazz =    obj.getClass().getComponentType();
097 
098             if (arrayElementClazz.isPrimitive()) {
099                 result += length * sizes.getPrimitiveArrayElementSize(arrayElementClazz);
100             
101             else {
102                 for    (int i = 0;    i <    length;    i++) {
103                     result +=    sizes.getPointerSize() + _estimate(Array.get(obj, i));
104                 }
105             }
106         }
107         
108         return result;
109     }
110 }





Recentemente alterei este código significativamente para incluir o novo IdentityHashMap e adicionar suporte a Strings internas. Isto não teria sido possível sem um bom conjunto para testes unitários. Normalmente eu não incluo meus testes unitários nas newsletters, já que ocupam muito espaço. Entretanto, eu gostaria de saber se você tiver um JDK 1.4.x em que os resultados são diferentes. O teste unitário deu certo na minha máquina, usando JDK 1.4.2, build 1.4.2-b28.


001 import java.util.*;
002 import junit.framework.TestCase;
003 import junit.swingui.TestRunner;
004 
005 public class MemoryCounterTest extends TestCase {
006     public static void main(String[] args) {
007         TestRunner.run(MemoryCounterTest.class);
008     }
009 
010     private static final MemoryCounter mc = new MemoryCounter();
011 
012     public MemoryCounterTest(String name) {
013         super(name);
014     }
015 
016     public void testString() {
017         assertEquals(64, mc.estimate(new String("Hello World!")));
018     }
019 
020     public void testIntegerToString() {
021         for (int i=0; i<1; i++) {
022             assertEquals(72, mc.estimate("" + i));
023         }
024     }
025 
026     static class Entry implements Map.Entry {
027         final Object key;
028         Object value;
029         final int hash;
030         Entry next;
031 
032         Entry(int h, Object k, Object v, Entry n) {
033             value = v;
034             next = n;
035             key = k;
036             hash = h;
037         }
038 
039         public Object getKey() {
040             return key;
041         }
042 
043         public Object getValue() {
044             return value;
045         }
046 
047         public Object setValue(Object value) {
048             return value;
049         }
050     }
051 
052     public void testHashMap() {
053         assertEquals(120, mc.estimate(new HashMap()));
054 
055         Byte[] all = new Byte[256];
056         for (int i = -128; i < 128; i++) {
057             all[i + 128new Byte((bytei);
058         }
059         assertEquals(5136, mc.estimate(all));
060 
061         HashMap hm = new HashMap();
062         for (int i = -128; i < 128; i++) {
063             hm.put("" + i, new Byte((bytei));
064         }
065         assertEquals(30776, mc.estimate(hm));
066     }
067 
068     public void testVector() {
069         assertEquals(80, mc.estimate(new Vector(10)));
070     }
071 
072     public void testObject() {
073         assertEquals(8, mc.estimate(new Object()));
074     }
075 
076     public void testInteger() {
077         assertEquals(16, mc.estimate(new Integer(1)));
078     }
079 
080     public void testCharArray() {
081         assertEquals(40, mc.estimate("Hello World!".toCharArray()));
082     }
083 
084     public void testByte() {
085         assertEquals(16, mc.estimate(new Byte((byte10)));
086     }
087 
088     public void testThreeBytes() {
089         assertEquals(16, mc.estimate(new ThreeBytes()));
090     }
091 
092     public void testSixtyFourBooleans() {
093         assertEquals(72, mc.estimate(new SixtyFourBooleans()));
094     }
095 
096     public void testThousandBooleansObjects() {
097         Boolean[] booleans = new Boolean[1000];
098 
099         for (int i = 0; i < booleans.length; i++)
100             booleans[inew Boolean(true);
101 
102         assertEquals(20016, mc.estimate(booleans));
103     }
104 
105     public void testThousandBytes() {
106         assertEquals(1016, mc.estimate(new byte[1000]));
107     }
108 
109     public void testEmptyArrayList() {
110         assertEquals(80, mc.estimate(new ArrayList()));
111     }
112 
113     public void testFullArrayList() {
114         ArrayList arrayList = new ArrayList(10000);
115 
116         for (int i = 0; i < 10000; i++) {
117             arrayList.add(new Object());
118         }
119 
120         assertEquals(120040, mc.estimate(arrayList));
121     }
122 
123     public void testFullLinkedList() {
124         LinkedList linkedList = new LinkedList();
125 
126         for (int i = 0; i < 10000; i++) {
127             linkedList.add(new Object());
128         }
129 
130         assertEquals(320048, mc.estimate(linkedList));
131     }
132 
133     public void testComplexClass() {
134         assertEquals(48, mc.estimate(new ComplexClass()));
135     }
136 
137     public void testBooleanArray() {
138         assertEquals(27, mc.estimate(new boolean[11]));
139     }
140 
141     public void testShortArray() {
142         assertEquals(38, mc.estimate(new short[11]));
143     }
144 
145     public void testIntArray() {
146         assertEquals(60, mc.estimate(new int[11]));
147     }
148 
149     public void testFloatArray() {
150         assertEquals(60, mc.estimate(new float[11]));
151     }
152 
153     public void testLongArray() {
154         assertEquals(104, mc.estimate(new long[11]));
155     }
156 
157     public void testDoubleArray() {
158         assertEquals(104, mc.estimate(new double[11]));
159     }
160 
161     static class ThreeBytes {
162         byte b0;
163         byte b1;
164         byte b2;
165     }
166 
167     private static class ComplexClass {
168         ComplexClass cc = this;
169         boolean z;
170         byte b;
171         char c;
172         double d;
173         float f;
174         int i;
175         long l;
176         short s;
177     }
178 
179     private static class SixtyFourBooleans {
180         boolean a0;
181         boolean a1;
182         boolean a2;
183         boolean a3;
184         boolean a4;
185         boolean a5;
186         boolean a6;
187         boolean a7;
188         boolean b0;
189         boolean b1;
190         boolean b2;
191         boolean b3;
192         boolean b4;
193         boolean b5;
194         boolean b6;
195         boolean b7;
196         boolean c0;
197         boolean c1;
198         boolean c2;
199         boolean c3;
200         boolean c4;
201         boolean c5;
202         boolean c6;
203         boolean c7;
204         boolean d0;
205         boolean d1;
206         boolean d2;
207         boolean d3;
208         boolean d4;
209         boolean d5;
210         boolean d6;
211         boolean d7;
212         boolean e0;
213         boolean e1;
214         boolean e2;
215         boolean e3;
216         boolean e4;
217         boolean e5;
218         boolean e6;
219         boolean e7;
220         boolean f0;
221         boolean f1;
222         boolean f2;
223         boolean f3;
224         boolean f4;
225         boolean f5;
226         boolean f6;
227         boolean f7;
228         boolean g0;
229         boolean g1;
230         boolean g2;
231         boolean g3;
232         boolean g4;
233         boolean g5;
234         boolean g6;
235         boolean g7;
236         boolean h0;
237         boolean h1;
238         boolean h2;
239         boolean h3;
240         boolean h4;
241         boolean h5;
242         boolean h6;
243         boolean h7;
244     }
245 }


Eu ficaria feliz de saber se você achou essa newsletter útil. Simplesmente me envie um email contando como isso pode lhe ajudar.

Abraços,

Heinz

P.S. O código original tinha vários comentários. Eu removi eles para não poluir a newsletter.

Sobre a <i>The Java Specialists Newsletter</i>
Este artigo é fruto de uma parceria entre o GUJ e o autor da The Java Specialists Newsletter, Dr. Heinz M. Kabutz. Originalmente publicadas em inglês e enviadas para milhares de leitores ao redor do planeta, o GUJ propô-se a realizar a tradução para o Português do conteúdo, disponibilizando assim um ótimo material para um número ainda maior de leitores.

Você encontra o documento original, assim como newsletters anteriores, no site oficial, em http://www.javaspecialists.co.za.

--------------------------------------------------------------------------------

Copyright 2000-2003 Maximum Solutions, South Africa
Reprint Rights. Copyright subsists in all the material included in this email, but you may freely share the entire newsletter with anyone you feel may be interested, and you may reprint excerpts both online and offline provided that you acknowledge the source as follows: This material from The Java(tm) Specialists' Newsletter by Maximum Solutions (South Africa). Please contact Maximum Solutions for more information.

Java and Sun are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. Maximum Solutions is independent of Sun Microsystems, Inc.


Apoiado e desenvolvido por Caelum Cursos Java - Copyright © 2001-2008 GUJ