After doing all these preparations, the following is the standard implementation of this graphic traversal:
Public Static IObjectProfilenode Profile (Object Obj)
{
Final Identityhashmap Visited = new identityhashmap ();
Final ObjectProfilenode root = createprofiletree (Obj, Visited,
Class_metadata_cache);
FinishProfiletree (root);
Return root;
}
Private Static ObjectProfilenode CreateProfiletree (Object Obj,
Identityhashmap Visited,
Map metadataMap)
{
Final ObjectProfilenode root = New ObjectProfilenode (NULL, OBJ, NULL);
Final LinkedList Queue = new linkedlist ();
Queue.addfirst (root);
Visited.put (Obj, root);
Final classaccessprivilegedaction caction =
New classaccessprivilegedaction ();
Final FieldAccessPrivilegeDaction Faaction =
New FieldAccessprivilegedAction ();
While (! queue.Isempty ())
{
Final ObjectProfilenode Node = (ObjectProfilenode) Queue.RemoveFirst ();
Obj = node.m_obj;
Final class objclass = obj.getClass ();
IF (Objclass.isaRray ())
{
Final Int ArrayLength = array.getlength (obj);
Final class componenttype = objclass.getComponentType ();
// Add shell pseudo-node:
Final AbstractShellProfilenode shell =
New ArrayshellProfilenode (Node, Objclass, Arraylength);
Shell.m_size = SizeofArrayShell (ArrayLength, ComponentType);
Node.m_shell = shell;
node.addfieldref (shell);
IF (! companyType.isprimitive ())
{
// Traverse Each Array Slot:
For (int i = 0; i { Final Object Ref = array.get (Obj, i); IF (Ref! = null) { ObjectProfilenode Child = (ObjectProfilenode) Visited.get (REF); IF (Child! = NULL) child.m_refcount; Else { Child = New ObjectProfilenode (Node, Ref, New arrayindexlink (node.m_link, i)); Node.addfieldref (child); Queue.Addlast (child); Visited.put (ref, child); } } } } } Else // The Object IS of A Non-Array Type { Final ClassMetAdata metadata = GetClassmetadata (Objclass, MetadataMap, Caection, FaAction); Final Field [] Fields = metadata.m_reffield; // Add shell pseudo-node: Final AbstractShellProfilenode shell = New ObjectshellProfilenode (Node, Metadata.m_primitivefieldcount, Metadata.m_reffields.length); Shell.m_size = metadata.m_shellsize; Node.m_shell = shell; node.addfieldref (shell); // Traverse All Non-Null Ref Fields: For (int f = 0, flimit = fields.Length; f { Final Field Field = Fields [f]; Final Object Ref; Try // TO GET The Field Value: { REF = Field.get (OBJ); } Catch (Exception E) { Throw new runtimeException ("Cannot get Field [" Field.getname () "] of class [" Field.getDeclaringclass () .getname () "]:" E.toTOString ()); } IF (Ref! = null) { ObjectProfilenode Child = (ObjectProfilenode) Visited.get (REF); IF (Child! = NULL) child.m_refcount; Else { Child = New ObjectProfilenode (Node, Ref, New classfieldlink (field); Node.addfieldref (child); Queue.Addlast (child); Visited.put (ref, child); } } } } } Return root; } Private static void finishprofiletree (ObjectProfilenode Node) { Final LinkedList Queue = new linkedlist (); IOBJECTPROFILENODE LASTFINISHED = NULL; While (Node! = NULL) { // Note That An Unfinished Nonshell Node Has ITS Child Count // in m_size and m_children [0] is its shell node: IF ((node.m_size == 1) || (Lastfinished == Node.m_children [1])) { Node.finish (); Lastfinished = node; } Else { Queue.addfirst (Node); For (INT i = 1; i { Final IOBJECTPROFILENODE CHILD = node.m_children [i]; Queue.addfirst (child); } } Queue.Isempty ()) Return; Else Node = (ObjectProfilenode) Queue.RemoveFirst (); } } The code is the "ATTACK of the Clones" to be distinguished from "Attack of The Clones". As mentioned earlier, it caches reflective metadata to improve performance, and uses an object that identifies a hash map to mark access. The PROFILE () method begins with the original object graphics of the spanning tree of IObjectProfilenode from the width priority, and the fast rear sequence traversal of all node sizes is completed. PROFILE () Returns an IObjectProfilenode, that is, the root generated tree, its size is the size of the entire graph. Of course, the output of Profile () is only useful when I have a good way to extend it. For this purpose, each IObjectProfilenode must support tests that are made together by node visitors and node filters: Interface IObjectProfilenode { Interface inodefilter { Boolean Accept (IObjectProfilenode Node); } // end of nested interface Interface inodevisitor { / ** * Pre-Order Visit. * / Void Previsit (IObjectProfilenode Node); / ** * Post-Order Visit. * / Void Postvisit (IObjectProfilenode Node); } // end of nested interface Boolean Traverse (Inodefilter Filter, Inodevisitor Visitor); ... } // end of interface The node visitor operates only when the accompany filter receives the null or the filter receives the node. For the sake of easy, the child node of the node is only tested when the node itself has been tested. Pre-sequence traversal and back sequence traversal access support. The size of the Java.lang.Object handler and all primary data are set in a pseudo code, which is attached to each "real" node representing the object instance. This processing program node can be displayed in IObjectProfilenode.Shell (), or it can also be displayed in the IObjectProfilenode.children () list: the purpose is to write data filters and visitors, allowing them to be in the same set of instantiated data types Considering primary data. How to implement filters and visitors are your business. As a starting point, class ObjectProfileFilters offers several useful stack filters that help you in node size, node dimensions related to the size of the parent node, node size related to root objects, etc. Up to cut the large object tree. The ObjectProfilervisitors class contains IObjectProfilenode.dump () uses the default accessor, also contains visits that can be created to create an XML dump for higher-level objects. It is also easy to convert the configuration file to SwingTreeModel. For ease of understanding, we created a complete dump of two string arrangements mentioned above: Public Class Main { Public static void main (string [] args) { Object obj = new string [] {New String ("javaworld"), NEW STRING ("javaworld")} IOBJECTPROFILENODE Profile = ObjectProfiler.profile (OBJ); System.out.println ("Obj size =" profile.size () "bytes"); System.out.println (Profile.dump ()); } } // End of class The code is as follows: Obj size = 106 bytes 106-> : string [] 58 (54.7%) -> [0]: String 34 (32.1%) -> String # value: char [], refcount = 2 34 (32.1%) -> 24 (22.6%) -> 24 (22.6%) -> 24 (22.6%) -> [1]: String 24 (22.6%) -> In fact, as mentioned earlier, the internal character arrangement (access by java.lang.string # value) can be shared by two strings. Even if ObjectProfiler.profile () points the slave relationship of the arrangement points to the first discovered string, it still notifies the array sharing (as shown in its next code REFCOUNT = 2). Simple sizeof () ObjectProfiler.profile () creates a node graphic, which is generally several times that of the original object graphic. If you only need the root object size, you can use faster and more efficient methods ObjectProfiler.sizeOf (), which can be achieved throughout the depth of unstacking. More examples We apply the Profile () and SIZEOF () functions into a pair of examples. JavaString is a reputable storage waste because they are too common, and the efficiency of the usage mode of normal strings is quite low. I believe that you understand that ordinary string series operators typically produce unmacked String. The following code: String Obj = "java" new string ("world"); Generate the following profiles: Obj size = 80 bytes 80-> : string 56 (70%) -> String # value: char [] 56 (70%) -> 24 (30%) -> Obj = New String ("Java" New String ("World")) Obj size = 58 BYTES 58-> : string 34 (58.6%) -> String # value: char [] 34 (58.6%) -> 24 (41.4%) -> Obviously, if you assign string properties constructed by a series operator or StringBuffer.toTRING () function to many objects (these two cases are actually very relevant), and if you change to use concat () or string replication If you want to improve memory consumption. In order to discuss this problem, I gave a slightly affectionate example, below this visitor / filter checks the object, and reports all the non-compact string inside: Class Stringinspector Implements IOBJECTPROFILENODE.INODEFILTER, IObjectProfilenode.inodevisitor { Public Boolean Accept (iObjectProfilenode Node) { m_node = NULL; Final Object obj = node.object (); IF ((Obj! = null) && (node.parent ()! = null) { Final Object Parentobj = node.parent () .Object (); IF ((Obj.getClass () == char [] .class) && () == String.class)))) { INT Wasted = ((char []) obj) .length - (String) Parentobj) .length (); IF (Wasted> 0) { m_node = node.parent (); m_wasted = m_nodewasted = Wasted; } } } Return True; } Public void Previsit (IObjectProfilenode Node) { IF (M_Node! = NULL) System.out.println (ObjectProfiler.pathname (m_node.path ()) ":" m_nodewasted "bytes Wasted"); } Public void Postvisit (IObjectProfilenode Node) { // do nothing } Int Wasted () { Return 2 * m_wasted; } Private IObjectProfilenode M_Node; Private int m_nodewasted, m_wasted; }; // end of local clas IOBJECTPROFILENODE Profile = ObjectProfiler.profile (OBJ); Stringinspector Si = new stringinspector (); Profile.Traverse (Si, Si); System.out.Println ("Wasted" Si.wasted () "BYTES (Out of" Profile.size () ")"); In order to use SizeOf (), let's take a look at LinkedList () vs arraylist (). This code breeds a list of 1000 empty references: List obj = new linkedList (); // or arraylist For (int i = 0; i <1000; i) obj.add (null); IOBJECTPROFILENODE Profile = ObjectProfiler.profile (OBJ); System.out.println ("Obj size =" profile.size () "bytes"); The size of the resulting structure is the store sum of the list implemented. For LINKEDLIST and ArrayList collection, the SIZEOF () reports 20, 040 and 4, 112 bytes respectively. Even if ArrayList grows internal capacity before its size (this will lose almost 50% of the capacity; this is to reimburse the cost of insertion constant), its aligned-based design is much higher than LinkedList ( ) Dual link list implementation, this list implementation creates 20-byte nodes to store each value (this is not said that you should not use LinkedList: They guarantee the performance of unpaid constant insertion, in other things This performance.) limit ObjectProfiler's method is not perfect. Another serious problem is that the Java object can share non-static data in addition to the problem of ignoring the storage queue in our previously explained, for example, when the field points to the global Singleton and other shared content, these content can be shared. Take DecimalFormat.getPercentInstance () as an example. Although he returns a new Numberformat each time, all of these Numberformat usually share local.getDefault () Singleton. So, even if SIZEOF (Decimalformat.getPercentInstance ()) reports 1,111 bytes each time, he is estimated too high. This is actually just the performance of another conceptual difficult point in the size measurement of the Java object. In this case, ObjectProfiler.SizeDelta (Object Base, Object Obj) is easy to get: This method traverses the rooted object graphics, and then configures OBJ using accessible objects during the first traversal. Therefore, the results can be effectively calculated as the total size of the data that does not seem to belong to the OBJ owned by Base. In other words, the amount of memory required to instantiate a given OBJ is equal to the existing amount of the base (the shared object has been deleted). SizeDelta (Decimalformat.getPercentInstance (), DecimalFormat.GetPercentInstance ()) Report: 741 bytes of each subsequence format requires 741 bytes, compared to the more accurate 752-byte of the Java Tip 130's SizeOf measurement, appears. The deviation of a small number of bytes is much better than the original sizeof () estimation. Another type of data that ObjectProfiler cannot see is local storage allocation. The result of java.nio.bytebuffer.allocate (1000) is a 1050-byte structure allocated by JVM, but bytebuffer.allocateDirect (1000) looks only to 140 bytes; this is because of true storage is in local storage distributed. At this point you need to give up pure Java, and turn to an analyzer based on JVM analyzer interface (JVMPI). Another fairly vague example of the same problem is: Only 20 bytes report only during the measurement of throwable. ObjectProfiler.SizeOf (New throwable ()), this is 272 bytes reported to the class SizeOf of Java TIP 130. The result of a large phase. The reason is because there is a hidden domain in Throwable: Private Transient Object Backtrace; JVM uses a special way to handle this hidden domain: he does not display in the reflection call, even if its definition is seen in the JDK source file. Obviously, JVM uses this property of the object to store some 250 bytes of local data that supports the stack backtrack. Finally, if you use java.lang.ref. * In the process of analysis, the result will be confused (for example, the result may fluctuate between the SIZEOF () call of the same object). This is because weak references create extra parallel in the application, and the absolute facts of this graphic may modify the reachable status of weak references. Moreover, the public entering java.lang.ref.Reference internal structure, this act of ObjectProfiler is not a matter of pure Java code. Enhance the traversal code to avoid all non-strong reference objects (it is not very determined whether the data attribute of this root object is in the first location), which may be the best choice. to sum up This article discusses how to build a pure Java object analyzer. My experience is to analyze the large data structure by simple method such as ObjectProfiler.profile (), which can easily save more than 10 percent of memory consumption. This approach is the supplement of the commercial analyzer, and the business analyzer is also a very light (not graphic) view that is intended to demonstrate the occurrence of JVM stacks. If there is no other thing, take a look at the object graphics is also very beneficial. About author Vladimir Roubtsov has a 14-year experience in the use of various language programming since 1995, including Java. Currently, as a senior engineer, he developed a company software for Austin, Texas.