Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Sizeof for Java

Object sizing revisited

  • Print
  • Feedback

Page 4 of 6

Of course, profile()'s output is useful only if I have a good way to explore it. To this end, every IObjectProfileNode supports examination by a node visitor together with a node filter:

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


A node visitor gets a shot at doing something with a tree node only if the accompanying filter is null or if the filter accepts the node. For simplicity, the node's children are examined only if the node itself has been examined. Both pre- and post-order visits are supported. The size contributions from the java.lang.Object shell plus all primitive data fields are lumped together in a pseudo-node attached to every "real" node representing an object instance. Such shell nodes are accessible via IObjectProfileNode.shell() and also show up in the IObjectProfileNode.children() list: the idea is to be able to write data filters and visitors that consider primitive data overhead on equal footing with instantiable data types.

It is up to you how to implement filters and visitors. As a starting point, the ObjectProfileFilters class (see this article's download) offers several useful stock filters that help prune large object trees based on node size, node size relative to its parent's size, node size relative to the root object, and so on. The ObjectProfilerVisitors class contains the default visitor used by IObjectProfileNode.dump(), as well as a visitor that can create an XML dump for more sophisticated object browsing. It is also easy to turn a profile into a Swing TreeModel.

As an illustration, let's do a full dump of the two-string array object 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


This code produces:

obj size = 106 bytes
  106 -> <INPUT> : String[]
    58 (54.7%) -> <INPUT>[0] : String
      34 (32.1%) -> String#value : char[], refcount=2
        34 (32.1%) -> <shell: char[], length=9>
      24 (22.6%) -> <shell: 3 prim/1 ref fields>
    24 (22.6%) -> <shell: String[], length=2>
    24 (22.6%) -> <INPUT>[1] : String
      24 (22.6%) -> <shell: 3 prim/1 ref fields>


Indeed, as explained earlier, the internal character array (referenced by java.lang.String#value) is shared between both strings. Even though ObjectProfiler.profile() assigns the ownership of this array to the first discovered string, it notices that the array is shared (shown by refcount=2 next to it).

Simply sizeof()

ObjectProfiler.profile() creates a node graph whose size overhead is typically several times the size of the original object graph. If you are interested only in the root object size, you can use a faster and more efficient ObjectProfiler.sizeof() method (see this article's download for the actual code), implemented via a nonrecursive depth-first traversal.

  • Print
  • Feedback

Resources