Java Specialists' Java Training Europehome of the java specialists' newsletter

The Java Specialists' Newsletter
Issue 0782003-09-29 Category: Performance Java version:

Subscribe Free RSS Feed

Contador de Memória para Java 1.4

by Dr. Heinz M. Kabutz
Special Thank You! I would like to thank Rafael Steil from the Grupo de Usuarios Java - GUJ, from Brazil for translating our newsletters into Portuguese. In addition, I would like to thank Vanessa Sabino for assisting Rafael in the translation work. Disclaimer: Since I do not know any Portuguese, I make no warranty for the accuracy of the translation. Heinz

Would you like to really understand Java concurrency? Join us for an in-depth study of how threading works in Java. During the course, you will learn how to write correct and fast multi-threaded Java code. Please click here if you would like to learn more.

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.

import java.util.*;

public class MemorySizes {
  private final Map primitiveSizes = new IdentityHashMap() {
    {
      put(boolean.class, new Integer(1));
      put(byte.class, new Integer(1));
      put(char.class, new Integer(2));
      put(short.class, new Integer(2));
      put(int.class, new Integer(4));
      put(float.class, new Integer(4));
      put(double.class, new Integer(8));
      put(long.class, new Integer(8));
    }
  };
  public int getPrimitiveFieldSize(Class clazz) {
    return ((Integer) primitiveSizes.get(clazz)).intValue();
  }
  public int getPrimitiveArrayElementSize(Class clazz) {
    return getPrimitiveFieldSize(clazz);
  }
  public int getPointerSize() {
    return 4;
  }
  public int getClassSize() {
    return 8;
  }
}
  

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.

import java.lang.reflect.*;
import java.util.*;

/**
 * Esta classe pode estimar o quanto de memória um objeto utiliza. Ela é
 * bastante precisa para o JDK 1.4.2.  É baseada na newsletter #29.
 */
public final class MemoryCounter {
  private static final MemorySizes sizes = new MemorySizes();
  private final Map visited = new IdentityHashMap();
  private final Stack stack = new Stack();

  public synchronized long estimate(Object obj) {
    assert visited.isEmpty();
    assert stack.isEmpty();
    long result = _estimate(obj);
    while (!stack.isEmpty()) {
      result += _estimate(stack.pop());
    }
    visited.clear();
    return result;
  }

  private boolean skipObject(Object obj) {
    if (obj instanceof String) {
            // isto não causará vazamento de memória já que
      // Strings internos não usados serão jogados fora
      if (obj == ((String) obj).intern()) {
        return true;
      }
    }
    return (obj == null)
        || visited.containsKey(obj);
  }

  private long _estimate(Object obj) {
    if (skipObject(obj)) return 0;
    visited.put(obj, null);
    long result = 0;
    Class clazz = obj.getClass();
    if (clazz.isArray()) {
      return _estimateArray(obj);
    }
    while (clazz != null) {
      Field[] fields = clazz.getDeclaredFields();
      for (int i = 0; i < fields.length; i++) {
        if (!Modifier.isStatic(fields[i].getModifiers())) {
          if (fields[i].getType().isPrimitive()) {
            result += sizes.getPrimitiveFieldSize(
                fields[i].getType());
          } else {
            result += sizes.getPointerSize();
            fields[i].setAccessible(true);
            try {
              Object toBeDone = fields[i].get(obj);
              if (toBeDone != null) {
                stack.add(toBeDone);
              }
            } catch (IllegalAccessException ex) { assert false; }
          }
        }
      }
      clazz = clazz.getSuperclass();
    }
    result += sizes.getClassSize();
    return roundUpToNearestEightBytes(result);
  }

  private long roundUpToNearestEightBytes(long result) {
    if ((result % 8) != 0) {
      result += 8 - (result % 8);
    }
    return result;
  }

  protected long _estimateArray(Object obj) {
    long result = 16;
    int length = Array.getLength(obj);
    if (length != 0) {
      Class arrayElementClazz = obj.getClass().getComponentType();
      if (arrayElementClazz.isPrimitive()) {
        result += length *
            sizes.getPrimitiveArrayElementSize(arrayElementClazz);
      } else {
        for (int i = 0; i < length; i++) {
          result += sizes.getPointerSize() +
              _estimate(Array.get(obj, i));
        }
      }
    }
    return result;
  }
}

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.

import java.util.*;
import junit.framework.TestCase;
import junit.swingui.TestRunner;

public class MemoryCounterTest extends TestCase {
  public static void main(String[] args) {
    TestRunner.run(MemoryCounterTest.class);
  }

  private static final MemoryCounter mc = new MemoryCounter();

  public MemoryCounterTest(String name) {
    super(name);
  }

  public void testString() {
    assertEquals(64, mc.estimate(new String("Hello World!")));
  }

  public void testIntegerToString() {
    for (int i=0; i<1; i++) {
      assertEquals(72, mc.estimate("" + i));
    }
  }

  static class Entry implements Map.Entry {
    final Object key;
    Object value;
    final int hash;
    Entry next;

    Entry(int h, Object k, Object v, Entry n) {
      value = v;
      next = n;
      key = k;
      hash = h;
    }

    public Object getKey() {
      return key;
    }

    public Object getValue() {
      return value;
    }

    public Object setValue(Object value) {
      return value;
    }
  }

  public void testHashMap() {
    assertEquals(120, mc.estimate(new HashMap()));

    Byte[] all = new Byte[256];
    for (int i = -128; i < 128; i++) {
      all[i + 128] = new Byte((byte) i);
    }
    assertEquals(5136, mc.estimate(all));

    HashMap hm = new HashMap();
    for (int i = -128; i < 128; i++) {
      hm.put("" + i, new Byte((byte) i));
    }
    assertEquals(30776, mc.estimate(hm));
  }

  public void testVector() {
    assertEquals(80, mc.estimate(new Vector(10)));
  }

  public void testObject() {
    assertEquals(8, mc.estimate(new Object()));
  }

  public void testInteger() {
    assertEquals(16, mc.estimate(new Integer(1)));
  }

  public void testCharArray() {
    assertEquals(40, mc.estimate("Hello World!".toCharArray()));
  }

  public void testByte() {
    assertEquals(16, mc.estimate(new Byte((byte) 10)));
  }

  public void testThreeBytes() {
    assertEquals(16, mc.estimate(new ThreeBytes()));
  }

  public void testSixtyFourBooleans() {
    assertEquals(72, mc.estimate(new SixtyFourBooleans()));
  }

  public void testThousandBooleansObjects() {
    Boolean[] booleans = new Boolean[1000];

    for (int i = 0; i < booleans.length; i++)
      booleans[i] = new Boolean(true);

    assertEquals(20016, mc.estimate(booleans));
  }

  public void testThousandBytes() {
    assertEquals(1016, mc.estimate(new byte[1000]));
  }

  public void testEmptyArrayList() {
    assertEquals(80, mc.estimate(new ArrayList()));
  }

  public void testFullArrayList() {
    ArrayList arrayList = new ArrayList(10000);

    for (int i = 0; i < 10000; i++) {
      arrayList.add(new Object());
    }

    assertEquals(120040, mc.estimate(arrayList));
  }

  public void testFullLinkedList() {
    LinkedList linkedList = new LinkedList();

    for (int i = 0; i < 10000; i++) {
      linkedList.add(new Object());
    }

    assertEquals(320048, mc.estimate(linkedList));
  }

  public void testComplexClass() {
    assertEquals(48, mc.estimate(new ComplexClass()));
  }

  public void testBooleanArray() {
    assertEquals(27, mc.estimate(new boolean[11]));
  }

  public void testShortArray() {
    assertEquals(38, mc.estimate(new short[11]));
  }

  public void testIntArray() {
    assertEquals(60, mc.estimate(new int[11]));
  }

  public void testFloatArray() {
    assertEquals(60, mc.estimate(new float[11]));
  }

  public void testLongArray() {
    assertEquals(104, mc.estimate(new long[11]));
  }

  public void testDoubleArray() {
    assertEquals(104, mc.estimate(new double[11]));
  }

  static class ThreeBytes {
    byte b0;
    byte b1;
    byte b2;
  }

  private static class ComplexClass {
    ComplexClass cc = this;
    boolean z;
    byte b;
    char c;
    double d;
    float f;
    int i;
    long l;
    short s;
  }

  private static class SixtyFourBooleans {
    boolean a0;
    boolean a1;
    boolean a2;
    boolean a3;
    boolean a4;
    boolean a5;
    boolean a6;
    boolean a7;
    boolean b0;
    boolean b1;
    boolean b2;
    boolean b3;
    boolean b4;
    boolean b5;
    boolean b6;
    boolean b7;
    boolean c0;
    boolean c1;
    boolean c2;
    boolean c3;
    boolean c4;
    boolean c5;
    boolean c6;
    boolean c7;
    boolean d0;
    boolean d1;
    boolean d2;
    boolean d3;
    boolean d4;
    boolean d5;
    boolean d6;
    boolean d7;
    boolean e0;
    boolean e1;
    boolean e2;
    boolean e3;
    boolean e4;
    boolean e5;
    boolean e6;
    boolean e7;
    boolean f0;
    boolean f1;
    boolean f2;
    boolean f3;
    boolean f4;
    boolean f5;
    boolean f6;
    boolean f7;
    boolean g0;
    boolean g1;
    boolean g2;
    boolean g3;
    boolean g4;
    boolean g5;
    boolean g6;
    boolean g7;
    boolean h0;
    boolean h1;
    boolean h2;
    boolean h3;
    boolean h4;
    boolean h5;
    boolean h6;
    boolean h7;
  }
}

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.

Performance Articles Related Java Course Discuss at The Java Specialist Club

    
Your Name

Your E-Mail

Your Phone

Your Company

Your Comment


Java Master
Java Concurrency
Design Patterns
In-House Courses



© 2010 Heinz Kabutz - All Rights Reserved Sitemap seo web design Catch22 Marketing
Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners. JavaSpecialists.eu is not connected to Oracle, Inc. and is not sponsored by Oracle, Inc.