/*
* Copyright (c) 2017, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.graalvm.util;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
/**
* Calculates approximate estimates of the size of an object graph.
*
* The result contains number of object headers {@link #getHeaderCount()}, number of pointers
* {@link #getPointerCount()} and size of the primitive data {@link #getPrimitiveByteSize()}.
*
* The methods {@link #getTotalBytes()} and {@link #getCompressedTotalBytes()} estimate the total
* number of bytes occupied. The real number of bytes occupied may vary due to different alignment
* or different header sizes on different virtual machines.
*/
public final class ObjectSizeEstimate {
private static final int UNCOMPRESSED_POINTER_SIZE = 8;
private static final int UNCOMPRESSED_HEADER_SIZE = 16;
private static final int COMPRESSED_POINTER_SIZE = 4;
private static final int COMPRESSED_HEADER_SIZE = 12;
/**
* Collect the size occupied by the object graph reachable from the given root object.
*
* @param root the starting point of the object graph traversal
*/
public static ObjectSizeEstimate forObject(Object root) {
return forObject(root, Integer.MAX_VALUE);
}
/**
* Collect the size occupied by the object graph reachable from the given root object.
*
* @param root the starting point of the object graph traversal
* @param maxDepth the maximum depth of the traversal
*/
public static ObjectSizeEstimate forObject(Object root, int maxDepth) {
return forObjectHelper(root, maxDepth);
}
private int headerCount;
private int pointerCount;
private int primitiveByteSize;
private ObjectSizeEstimate() {
}
public ObjectSizeEstimate add(ObjectSizeEstimate other) {
ObjectSizeEstimate result = new ObjectSizeEstimate();
result.headerCount = headerCount + other.headerCount;
result.primitiveByteSize = primitiveByteSize + other.primitiveByteSize;
result.pointerCount = pointerCount + other.pointerCount;
return result;
}
public ObjectSizeEstimate subtract(ObjectSizeEstimate other) {
ObjectSizeEstimate result = new ObjectSizeEstimate();
result.headerCount = headerCount - other.headerCount;
result.primitiveByteSize = primitiveByteSize - other.primitiveByteSize;
result.pointerCount = pointerCount - other.pointerCount;
return result;
}
public int getHeaderCount() {
return headerCount;
}
public int getPointerCount() {
return pointerCount;
}
public int getPrimitiveByteSize() {
return primitiveByteSize;
}
@Override
public String toString() {
return String.format("(#headers=%s, #pointers=%s, #primitiveBytes=%s, totalCompressed=%s, totalNonCompressed=%s)", headerCount, pointerCount, primitiveByteSize,
getCompressedTotalBytes(), getTotalBytes());
}
public int getCompressedTotalBytes() {
return headerCount * COMPRESSED_HEADER_SIZE + pointerCount * COMPRESSED_POINTER_SIZE + primitiveByteSize;
}
public int getTotalBytes() {
return headerCount * UNCOMPRESSED_HEADER_SIZE + pointerCount * UNCOMPRESSED_POINTER_SIZE + primitiveByteSize;
}
private void recordHeader() {
headerCount++;
}
private void recordPointer() {
pointerCount++;
}
private void recordPrimitiveBytes(int size) {
primitiveByteSize += size;
}
private static ObjectSizeEstimate forObjectHelper(Object object, int maxDepth) {
EconomicMap<Object, Object> identityHashMap = EconomicMap.create(Equivalence.IDENTITY_WITH_SYSTEM_HASHCODE);
ObjectSizeEstimate size = new ObjectSizeEstimate();
ArrayList<Object> stack = new ArrayList<>();
ArrayList<Integer> depthStack = new ArrayList<>();
stack.add(object);
depthStack.add(0);
identityHashMap.put(object, object);
while (!stack.isEmpty()) {
Object o = stack.remove(stack.size() - 1);
int depth = depthStack.remove(depthStack.size() - 1);
size.recordHeader();
Class<?> c = o.getClass();
if (c.isArray()) {
size.recordPrimitiveBytes(Integer.BYTES);
if (o instanceof byte[]) {
size.recordPrimitiveBytes(Byte.BYTES * ((byte[]) o).length);
} else if (o instanceof boolean[]) {
size.recordPrimitiveBytes(Byte.BYTES * ((boolean[]) o).length);
} else if (o instanceof char[]) {
size.recordPrimitiveBytes(Character.BYTES * ((char[]) o).length);
} else if (o instanceof short[]) {
size.recordPrimitiveBytes(Short.BYTES * ((short[]) o).length);
} else if (o instanceof int[]) {
size.recordPrimitiveBytes(Integer.BYTES * ((int[]) o).length);
} else if (o instanceof long[]) {
size.recordPrimitiveBytes(Long.BYTES * ((long[]) o).length);
} else if (o instanceof float[]) {
size.recordPrimitiveBytes(Float.BYTES * ((float[]) o).length);
} else if (o instanceof double[]) {
size.recordPrimitiveBytes(Byte.BYTES * ((double[]) o).length);
} else {
for (Object element : (Object[]) o) {
size.recordPointer();
if (element != null) {
if (depth < maxDepth && !identityHashMap.containsKey(element)) {
identityHashMap.put(element, null);
stack.add(element);
depthStack.add(depth + 1);
}
}
}
}
} else {
while (c != null) {
Field[] fields = c.getDeclaredFields();
for (Field f : fields) {
if (!Modifier.isStatic(f.getModifiers())) {
Class<?> type = f.getType();
if (type == Byte.TYPE) {
size.recordPrimitiveBytes(Byte.BYTES);
} else if (type == Boolean.TYPE) {
size.recordPrimitiveBytes(Byte.BYTES);
} else if (type == Character.TYPE) {
size.recordPrimitiveBytes(Character.BYTES);
} else if (type == Short.TYPE) {
size.recordPrimitiveBytes(Short.BYTES);
} else if (type == Integer.TYPE) {
size.recordPrimitiveBytes(Integer.BYTES);
} else if (type == Long.TYPE) {
size.recordPrimitiveBytes(Long.BYTES);
} else if (type == Float.TYPE) {
size.recordPrimitiveBytes(Float.BYTES);
} else if (type == Double.TYPE) {
size.recordPrimitiveBytes(Double.BYTES);
} else {
size.recordPointer();
if (maxDepth > 1) {
f.setAccessible(true);
try {
Object inner = f.get(o);
if (inner != null) {
if (depth < maxDepth && !identityHashMap.containsKey(inner)) {
identityHashMap.put(inner, null);
stack.add(inner);
depthStack.add(depth + 1);
}
}
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new UnsupportedOperationException("Must have access privileges to traverse object graph");
}
}
}
}
}
c = c.getSuperclass();
}
}
}
return size;
}
public static ObjectSizeEstimate zero() {
return new ObjectSizeEstimate();
}
@Override
public int hashCode() {
final int prime = 31;
return headerCount + prime * (pointerCount + prime * primitiveByteSize);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof ObjectSizeEstimate) {
ObjectSizeEstimate other = (ObjectSizeEstimate) obj;
return headerCount == other.headerCount && pointerCount == other.pointerCount && primitiveByteSize == other.primitiveByteSize;
}
return false;
}
}