标签:des style blog http color java 使用 os
对于一个有经验的JAVA程序员来说,了解一个或者其它的JAVA对象占用了多少内存,这将会非常有用。你可能已经听说过我们所生活的世界,存储容量将不再是一个问题,这个对于你的文本编辑器来说可能是对的(不过,打开一个包含大量的图片以及图表的文档,看看你的编辑器会消耗多少内存),对于一个专用服务器软件来说也可能是对的(至少在你的企业成长到足够大或者是在同一台服务器运行其它的软件之前),对于基于云的软件来说也可能是对的,如果你足够的富有可以花足够的钱可以买顶级的服务器硬件。
然而,现实是你的软件如果是受到了内存限制,需要做的是花钱优化它而不是尝试获取更好的硬件(原文:in the real world your software will once reach a point where it makes sense to spend money in its optimization rather than trying to obtain an even better hardware)(目前你可以获取到的最好的商业服务器是64G内存),此时你不得不分析你的应用程序来找出是哪个数据结构消耗了大部份的内存。对于这种分析任务,最好的工具就是一个好的性能分析工具,但是你可以在刚开始的时候,使用分析你代码中的对象这种用廉价的方式。这篇文章描述了使用基于Oracle
JDK的ClassIntrospector类,来分析你的应用程序内存消耗。
我曾经在文章字符串包装第1部分:将字符转换为字节中提到了JAVA对象内存结构,例如我曾经写过,在JAVA1.7.0_06以前,一个具有28个字符的字符串会占用104个字节,事实上,我在写这篇文章的时候,通过自己的性能分析器证实了我的计算结果。现在我们使用Oracle
JDK中特殊类sun.misc.Unsafe,通过纯JAVA来实现一个JAVA对象内省器(introspector)。
我们使用sun.misc.Unsafe的以下方法:
-
-
public native long objectFieldOffset(java.lang.reflect.Field field);
-
-
public native int arrayBaseOffset(java.lang.Class aClass);
-
-
public native int arrayIndexScale(java.lang.Class aClass);
-
-
public native int addressSize();
在sun.misc.Unsafe中有两个额外的内省方法:staticFieldBase及staticFieldOffset,但是在这篇文章中不会使用到。这两个方法对于非安全的读写静态方法会有用。
我们应如何找到一个对象的内存布局?
1、循环的在分析类及父类上调用Class.getDeclaredFields
,获取所有对象的字段,包括其父类中的字段;
2、针对非静态字段(通过Field.getModifiers() & Modifiers.STATIC
判断静态字段),通过使用Unsafe.objectFieldOffset
在其父类中获取一个字段的偏移量以及该字段的shallow(注:shallow指的是当前对象本身的大小)大小:基础类型的默认值及4个或8个字节的对象引用(更多看下面);
3、对数组来说,调用Unsafe.arrayBaseOffset及Unsafe.arrayIndexScale
,数组的整个shallow大小将会是 当前数组的偏移量+每个数组的大小*数组的长度(原文是:offset + scale * Array.getLength(array)
),当然了也包括对数组本身引用的大小(看前面提到的);
4、别忘了对象图的循环引用,因而就需要对前面已经分析过的对象进行跟踪记录(针对这些情况,推荐使用IdentityHashMap
)
Java对象引用大小是一个非常不确定的值(原文:Java Object
reference size is quite a virtual value),它可能是4个字节或者是8个字节,这个取决于你的JVM设置以及给了多少内存给JVM,针对32G以上的堆,它就总是8个字节,但是针对小一点的堆就是4个字节除非你在JVM设置里关掉设置-XX:-UseCompressedOops
(我不确定这个功能是在JVM的哪个版本加进来的,或者是默认是打开的)。结果就是,安全的方式获取对像引用的大小就是找到Object[]数组中一个元素的大小:unsafe.arrayIndexScale(
Object[].class )
,针对这种情况,Unsafe.addressSize
倒不实用了。
针对32G以下堆内存中例用4字节引用的一点小小注意。一个正常的4个字节的指针可以定位到4G地址空间任何地址。如果我们假设所有已分配的对象将通过8字节边界对齐,在我们的32位指针中我们将不再需要最低3位(这些位将总是等于零)。这意味着我们可以存储35位地址在32位中。(这一节附上原文如下:
A small implementation note on 4 byte references on under 32G heaps. A normal 4 byte pointer could addressany byte in 4G address space. If we will assume that all allocated objects will be aligned by 8 bytes boundary, we won’t
need 3 lowest bits in our 32 bit pointers anymore (these bits will always be equal to zeroes). This means that we can store 35 bit addresses in 32 bit value:)
-
32_bit_reference = ( int ) ( actual_64_bit_pointer >> 3 )
35位允许寻址 32位*8=4G*8=32G地址空间。
写这个工具时发现的其它的一些有趣的事情
1、要打印数组的内容,必须使用Arrays.toString(包括基本类型及对象数组);
2、你必须要小心 - 内省方法(introspection method)只接受对象作为字段值,因此你最终可能处在无限循环中:整型打包成整数,以便传递到内省的方法。里面你会发现一个Integer.value字段,并尝试再次内省了 - 瞧,你又回到了开始的地方!
3、要内省(introspect)对象数组中所有非空的值 - 这仅仅是间接的对象图中的外部level(原文:this is just an extra level of indirection in the object graph)
如何使用ClassIntrospector
类?仅需要实例化它并且在你的任意的对象中调用它的实例内省(introspect
)方法,它会返回一个ObjectInfo对象,这个对象与你的“根‘对象有关,这个对象将指向它的所有子项,我想这可能是足够的打印其toString方法的结果和/或调用ObjectInfo.getDeepSize方法(原文:I think it may be sufficient to print its toString
method
result and/or to call ObjectInfo.getDeepSize
method),它将通过你的”根“对象引用,返回你的所有对象的总内存消耗。
ClassIntrospector
不是线程安全的,但是你可以在同一个线程中任意多次调用内省(introspect
)方法。
总结:
1、你可以使用sun.misc.Unsafe
的这些方法获取Java对象的布局信息:objectFieldOffset
, arrayBaseOffset
and arrayIndexScale
;
2、Java对象引用的大小取决于你当前的环境,根据不同JVM的设置以及分配给JVM的内存大小,它可能是4个或者8个字节。在大于32G的堆中,对像引用的大小总会是8个字节,但是在一个比较小的堆中它就会是4个字节,除非关闭JVM设置:-XX:-UseCompressedOops
。
源码
ClassIntrospector:
-
import sun.misc.Unsafe;
-
-
import java.lang.reflect.Array;
-
import java.lang.reflect.Field;
-
import java.lang.reflect.Modifier;
-
import java.math.BigDecimal;
-
import java.util.*;
-
-
-
-
-
public class ClassIntrospector
-
{
-
public static void main(String[] args) throws IllegalAccessException {
-
final ClassIntrospector ci = new ClassIntrospector();
-
final Map<String, BigDecimal> map = new HashMap<String, BigDecimal>( 10);
-
map.put( "one", BigDecimal.ONE );
-
map.put( "zero", BigDecimal.ZERO );
-
map.put( "ten", BigDecimal.TEN );
-
final ObjectInfo res;
-
res = ci.introspect( "0123456789012345678901234567" );
-
-
-
-
-
-
-
System.out.println( res.getDeepSize() );
-
System.out.println( res );
-
}
-
-
-
private static class TestObj
-
{
-
protected final String[] strings = { "str1", "str2" };
-
protected final int[] ints = { 14, 16 };
-
private final Integer i = 28;
-
protected final BigDecimal bigDecimal = BigDecimal.ONE;
-
-
@Override
-
public String toString() {
-
return "TestObj{" +
-
"strings=" + (strings == null ? null : Arrays.asList(strings)) +
-
", ints=" + Arrays.toString( ints ) +
-
", i=" + i +
-
", bigDecimal=" + bigDecimal +
-
‘}‘;
-
}
-
}
-
-
-
private static class TestObjChild extends TestObj
-
{
-
private final boolean[] flags = { true, true, false };
-
private final boolean flag = false;
-
-
@Override
-
public String toString() {
-
return "TestObjChild{" +
-
"flags=" + Arrays.toString( flags ) +
-
", flag=" + flag +
-
‘}‘;
-
}
-
}
-
-
private static final Unsafe unsafe;
-
-
private static final int objectRefSize;
-
static
-
{
-
try
-
{
-
Field field = Unsafe.class.getDeclaredField("theUnsafe");
-
field.setAccessible(true);
-
unsafe = (Unsafe)field.get(null);
-
-
objectRefSize = unsafe.arrayIndexScale( Object[].class );
-
}
-
catch (Exception e)
-
{
-
throw new RuntimeException(e);
-
}
-
}
-
-
-
private static final Map<Class, Integer> primitiveSizes;
-
-
static
-
{
-
primitiveSizes = new HashMap<Class, Integer>( 10 );
-
primitiveSizes.put( byte.class, 1 );
-
primitiveSizes.put( char.class, 2 );
-
primitiveSizes.put( int.class, 4 );
-
primitiveSizes.put( long.class, 8 );
-
primitiveSizes.put( float.class, 4 );
-
primitiveSizes.put( double.class, 8 );
-
primitiveSizes.put( boolean.class, 1 );
-
}
-
-
-
-
-
-
-
-
-
public ObjectInfo introspect( final Object obj ) throws IllegalAccessException
-
{
-
try
-
{
-
return introspect( obj, null );
-
}
-
finally {
-
m_visited.clear();
-
}
-
}
-
-
-
private IdentityHashMap<Object, Boolean> m_visited = new IdentityHashMap<Object, Boolean>( 100 );
-
-
private ObjectInfo introspect( final Object obj, final Field fld ) throws IllegalAccessException
-
{
-
-
-
-
-
boolean isPrimitive = fld != null && fld.getType().isPrimitive();
-
boolean isRecursive = false;
-
if ( !isPrimitive )
-
{
-
if ( m_visited.containsKey( obj ) )
-
isRecursive = true;
-
m_visited.put( obj, true );
-
}
-
-
final Class type = ( fld == null || ( obj != null && !isPrimitive) ) ?
-
obj.getClass() : fld.getType();
-
int arraySize = 0;
-
int baseOffset = 0;
-
int indexScale = 0;
-
if ( type.isArray() && obj != null )
-
{
-
baseOffset = unsafe.arrayBaseOffset( type );
-
indexScale = unsafe.arrayIndexScale( type );
-
arraySize = baseOffset + indexScale * Array.getLength( obj );
-
}
-
-
final ObjectInfo root;
-
if ( fld == null )
-
{
-
root = new ObjectInfo( "", type.getCanonicalName(), getContents( obj, type ), 0, getShallowSize( type ),
-
arraySize, baseOffset, indexScale );
-
}
-
else
-
{
-
final int offset = ( int ) unsafe.objectFieldOffset( fld );
-
root = new ObjectInfo( fld.getName(), type.getCanonicalName(), getContents( obj, type ), offset,
-
getShallowSize( type ), arraySize, baseOffset, indexScale );
-
}
-
-
if ( !isRecursive && obj != null )
-
{
-
if ( isObjectArray( type ) )
-
{
-
-
final Object[] ar = ( Object[] ) obj;
-
for ( final Object item : ar )
-
if ( item != null )
-
root.addChild( introspect( item, null ) );
-
}
-
else
-
{
-
for ( final Field field : getAllFields( type ) )
-
{
-
if ( ( field.getModifiers() & Modifier.STATIC ) != 0 )
-
{
-
continue;
-
}
-
field.setAccessible( true );
-
root.addChild( introspect( field.get( obj ), field ) );
-
}
-
}
-
}
-
-
root.sort();
-
return root;
-
}
-
-
-
private static List<Field> getAllFields( final Class type )
-
{
-
if ( type.isPrimitive() )
-
return Collections.emptyList();
-
Class cur = type;
-
final List<Field> res = new ArrayList<Field>( 10 );
-
while ( true )
-
{
-
Collections.addAll( res, cur.getDeclaredFields() );
-
if ( cur == Object.class )
-
break;
-
cur = cur.getSuperclass();
-
}
-
return res;
-
}
-
-
-
private static boolean isObjectArray( final Class type )
-
{
-
if ( !type.isArray() )
-
return false;
-
if ( type == byte[].class || type == boolean[].class || type == char[].class || type == short[].class ||
-
type == int[].class || type == long[].class || type == float[].class || type == double[].class )
-
return false;
-
return true;
-
}
-
-
-
private static String getContents( final Object val, final Class type )
-
{
-
if ( val == null )
-
return "null";
-
if ( type.isArray() )
-
{
-
if ( type == byte[].class )
-
return Arrays.toString( ( byte[] ) val );
-
else if ( type == boolean[].class )
-
return Arrays.toString( ( boolean[] ) val );
-
else if ( type == char[].class )
-
return Arrays.toString( ( char[] ) val );
-
else if ( type == short[].class )
-
return Arrays.toString( ( short[] ) val );
-
else if ( type == int[].class )
-
return Arrays.toString( ( int[] ) val );
-
else if ( type == long[].class )
-
return Arrays.toString( ( long[] ) val );
-
else if ( type == float[].class )
-
return Arrays.toString( ( float[] ) val );
-
else if ( type == double[].class )
-
return Arrays.toString( ( double[] ) val );
-
else
-
return Arrays.toString( ( Object[] ) val );
-
}
-
return val.toString();
-
}
-
-
-
private static int getShallowSize( final Class type )
-
{
-
if ( type.isPrimitive() )
-
{
-
final Integer res = primitiveSizes.get( type );
-
return res != null ? res : 0;
-
}
-
else
-
return objectRefSize;
-
}
-
}
ObjectInfo:
-
import java.util.ArrayList;
-
import java.util.Collections;
-
import java.util.Comparator;
-
import java.util.List;
-
-
-
-
-
public class ObjectInfo {
-
-
public final String name;
-
-
public final String type;
-
-
public final String contents;
-
-
public final int offset;
-
-
public final int length;
-
-
public final int arrayBase;
-
-
public final int arrayElementSize;
-
-
public final int arraySize;
-
-
public final List<ObjectInfo> children;
-
-
public ObjectInfo(String name, String type, String contents, int offset, int length, int arraySize,
-
int arrayBase, int arrayElementSize)
-
{
-
this.name = name;
-
this.type = type;
-
this.contents = contents;
-
this.offset = offset;
-
this.length = length;
-
this.arraySize = arraySize;
-
this.arrayBase = arrayBase;
-
this.arrayElementSize = arrayElementSize;
-
children = new ArrayList<ObjectInfo>( 1 );
-
}
-
-
public void addChild( final ObjectInfo info )
-
{
-
if ( info != null )
-
children.add( info );
-
}
-
-
-
-
-
-
-
-
-
public long getDeepSize()
-
{
-
return length + arraySize + getUnderlyingSize( arraySize != 0 );
-
}
-
-
private long getUnderlyingSize( final boolean isArray )
-
{
-
long size = 0;
-
for ( final ObjectInfo child : children )
-
size += child.arraySize + child.getUnderlyingSize( child.arraySize != 0 );
-
if ( !isArray && !children.isEmpty() )
-
size += children.get( children.size() - 1 ).offset + children.get( children.size() - 1 ).length;
-
return size;
-
}
-
-
private static final class OffsetComparator implements Comparator<ObjectInfo>
-
{
-
@Override
-
public int compare( final ObjectInfo o1, final ObjectInfo o2 )
-
{
-
return o1.offset - o2.offset;
-
}
-
}
-
-
-
public void sort()
-
{
-
Collections.sort( children, new OffsetComparator() );
-
}
-
-
@Override
-
public String toString() {
-
final StringBuilder sb = new StringBuilder();
-
toStringHelper( sb, 0 );
-
return sb.toString();
-
}
-
-
private void toStringHelper( final StringBuilder sb, final int depth )
-
{
-
depth( sb, depth ).append("name=").append( name ).append(", type=").append( type )
-
.append( ", contents=").append( contents ).append(", offset=").append( offset )
-
.append(", length=").append( length );
-
if ( arraySize > 0 )
-
{
-
sb.append(", arrayBase=").append( arrayBase );
-
sb.append(", arrayElemSize=").append( arrayElementSize );
-
sb.append( ", arraySize=").append( arraySize );
-
}
-
for ( final ObjectInfo child : children )
-
{
-
sb.append( ‘\n‘ );
-
child.toStringHelper(sb, depth + 1);
-
}
-
}
-
-
private StringBuilder depth( final StringBuilder sb, final int depth )
-
{
-
for ( int i = 0; i < depth; ++i )
-
sb.append( ‘\t‘ );
-
return sb;
-
}
-
}
原文地址:http://java-performance.info/memory-introspection-using-sun-misc-unsafe-and-reflection/
使用sun.misc.Unsafe及反射对内存进行内省(introspection),布布扣,bubuko.com
使用sun.misc.Unsafe及反射对内存进行内省(introspection)
标签:des style blog http color java 使用 os
原文地址:http://blog.csdn.net/aigoogle/article/details/38396373