1+ package code .jvm .util ;
2+
3+ import java .lang .reflect .Array ;
4+ import java .lang .reflect .Field ;
5+ import java .lang .reflect .Modifier ;
6+ import java .util .ArrayList ;
7+ import java .util .Arrays ;
8+ import java .util .Collections ;
9+ import java .util .HashMap ;
10+ import java .util .IdentityHashMap ;
11+ import java .util .List ;
12+ import java .util .Map ;
13+
14+ import sun .misc .Unsafe ;
15+
16+ /**
17+ * This class could be used for any object contents/memory layout printing.
18+ *
19+ * @see https://blog.csdn.net/iter_zc/article/details/41822719
20+ */
21+ public class ClassIntrospector {
22+
23+ private static final Unsafe unsafe ;
24+ /**
25+ * Size of any Object reference
26+ */
27+ private static final int objectRefSize ;
28+
29+ static {
30+ try {
31+ Field field = Unsafe .class .getDeclaredField ("theUnsafe" );
32+ field .setAccessible (true );
33+ unsafe = (Unsafe ) field .get (null );
34+
35+ objectRefSize = unsafe .arrayIndexScale (Object [].class );
36+ } catch (Exception e ) {
37+ throw new RuntimeException (e );
38+ }
39+ }
40+
41+ /**
42+ * Sizes of all primitive values
43+ */
44+ private static final Map <Class , Integer > primitiveSizes ;
45+
46+ static {
47+ primitiveSizes = new HashMap <Class , Integer >(10 );
48+ primitiveSizes .put (byte .class , 1 );
49+ primitiveSizes .put (char .class , 2 );
50+ primitiveSizes .put (int .class , 4 );
51+ primitiveSizes .put (long .class , 8 );
52+ primitiveSizes .put (float .class , 4 );
53+ primitiveSizes .put (double .class , 8 );
54+ primitiveSizes .put (boolean .class , 1 );
55+ }
56+
57+ /**
58+ * Get object information for any Java object. Do not pass primitives to
59+ * this method because they will boxed and the information you will get will
60+ * be related to a boxed version of your value.
61+ *
62+ * @param obj Object to introspect
63+ * @return Object info
64+ * @throws IllegalAccessException
65+ */
66+ public ObjectInfo introspect (final Object obj ) throws IllegalAccessException {
67+ try {
68+ return introspect (obj , null );
69+ } finally { // clean visited cache before returning in order to make
70+ // this object reusable
71+ m_visited .clear ();
72+ }
73+ }
74+
75+ // we need to keep track of already visited objects in order to support
76+ // cycles in the object graphs
77+ private IdentityHashMap <Object , Boolean > m_visited = new IdentityHashMap <Object , Boolean >(
78+ 100 );
79+
80+ private ObjectInfo introspect (final Object obj , final Field fld )
81+ throws IllegalAccessException {
82+ // use Field type only if the field contains null. In this case we will
83+ // at least know what's expected to be
84+ // stored in this field. Otherwise, if a field has interface type, we
85+ // won't see what's really stored in it.
86+ // Besides, we should be careful about primitives, because they are
87+ // passed as boxed values in this method
88+ // (first arg is object) - for them we should still rely on the field
89+ // type.
90+ boolean isPrimitive = fld != null && fld .getType ().isPrimitive ();
91+ boolean isRecursive = false ; // will be set to true if we have already
92+ // seen this object
93+ if (!isPrimitive ) {
94+ if (m_visited .containsKey (obj ))
95+ isRecursive = true ;
96+ m_visited .put (obj , true );
97+ }
98+
99+ final Class type = (fld == null || (obj != null && !isPrimitive )) ? obj
100+ .getClass () : fld .getType ();
101+ int arraySize = 0 ;
102+ int baseOffset = 0 ;
103+ int indexScale = 0 ;
104+ if (type .isArray () && obj != null ) {
105+ baseOffset = unsafe .arrayBaseOffset (type );
106+ indexScale = unsafe .arrayIndexScale (type );
107+ arraySize = baseOffset + indexScale * Array .getLength (obj );
108+ }
109+
110+ final ObjectInfo root ;
111+ if (fld == null ) {
112+ root = new ObjectInfo ("" , type .getCanonicalName (), getContents (obj ,
113+ type ), 0 , getShallowSize (type ), arraySize , baseOffset ,
114+ indexScale );
115+ } else {
116+ final int offset = (int ) unsafe .objectFieldOffset (fld );
117+ root = new ObjectInfo (fld .getName (), type .getCanonicalName (),
118+ getContents (obj , type ), offset , getShallowSize (type ),
119+ arraySize , baseOffset , indexScale );
120+ }
121+
122+ if (!isRecursive && obj != null ) {
123+ if (isObjectArray (type )) {
124+ // introspect object arrays
125+ final Object [] ar = (Object []) obj ;
126+ for (final Object item : ar )
127+ if (item != null )
128+ root .addChild (introspect (item , null ));
129+ } else {
130+ for (final Field field : getAllFields (type )) {
131+ if ((field .getModifiers () & Modifier .STATIC ) != 0 ) {
132+ continue ;
133+ }
134+ field .setAccessible (true );
135+ root .addChild (introspect (field .get (obj ), field ));
136+ }
137+ }
138+ }
139+
140+ root .sort (); // sort by offset
141+ return root ;
142+ }
143+
144+ // get all fields for this class, including all superclasses fields
145+ private static List <Field > getAllFields (final Class type ) {
146+ if (type .isPrimitive ())
147+ return Collections .emptyList ();
148+ Class cur = type ;
149+ final List <Field > res = new ArrayList <Field >(10 );
150+ while (true ) {
151+ Collections .addAll (res , cur .getDeclaredFields ());
152+ if (cur == Object .class )
153+ break ;
154+ cur = cur .getSuperclass ();
155+ }
156+ return res ;
157+ }
158+
159+ // check if it is an array of objects. I suspect there must be a more
160+ // API-friendly way to make this check.
161+ private static boolean isObjectArray (final Class type ) {
162+ if (!type .isArray ())
163+ return false ;
164+ if (type == byte [].class || type == boolean [].class
165+ || type == char [].class || type == short [].class
166+ || type == int [].class || type == long [].class
167+ || type == float [].class || type == double [].class )
168+ return false ;
169+ return true ;
170+ }
171+
172+ // advanced toString logic
173+ private static String getContents (final Object val , final Class type ) {
174+ if (val == null )
175+ return "null" ;
176+ if (type .isArray ()) {
177+ if (type == byte [].class )
178+ return Arrays .toString ((byte []) val );
179+ else if (type == boolean [].class )
180+ return Arrays .toString ((boolean []) val );
181+ else if (type == char [].class )
182+ return Arrays .toString ((char []) val );
183+ else if (type == short [].class )
184+ return Arrays .toString ((short []) val );
185+ else if (type == int [].class )
186+ return Arrays .toString ((int []) val );
187+ else if (type == long [].class )
188+ return Arrays .toString ((long []) val );
189+ else if (type == float [].class )
190+ return Arrays .toString ((float []) val );
191+ else if (type == double [].class )
192+ return Arrays .toString ((double []) val );
193+ else
194+ return Arrays .toString ((Object []) val );
195+ }
196+ return val .toString ();
197+ }
198+
199+ // obtain a shallow size of a field of given class (primitive or object
200+ // reference size)
201+ private static int getShallowSize (final Class type ) {
202+ if (type .isPrimitive ()) {
203+ final Integer res = primitiveSizes .get (type );
204+ return res != null ? res : 0 ;
205+ } else
206+ return objectRefSize ;
207+ }
208+ }
0 commit comments