|
|
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
Page 5 of 6
Let's apply profile() and sizeof() machinery to a couple of quick examples.
Java Strings are notorious memory wasters because they are so ubiquitous and because common string usage patterns can be quite inefficient.
As I am sure you know, the natural string concatenation operator usually results in Strings that are not compact. This code:
String obj = "Java" + new String ("World");
produces the following profile:
obj size = 80 bytes
80 -> <INPUT> : String
56 (70%) -> String#value : char[]
56 (70%) -> <shell: char[], length=20>
24 (30%) -> <shell: 3 prim/1 ref fields>
The value character array holds 20 chars even though it only needs 9. Compare this with the result of "Java".concat ("World") or String obj = new String ("Java" + new String ("World")):
obj size = 58 bytes
58 -> <INPUT> : String
34 (58.6%) -> String#value : char[]
34 (58.6%) -> <shell: char[], length=9>
24 (41.4%) -> <shell: 3 prim/1 ref fields>
Obviously, if you allocate many objects with string properties constructed via the concatenation operator or StringBuffer.toString() (both cases are actually quite related under the hood), you will improve memory consumption if you instead use concat() or the String copy constructor.
As a more esoteric example of pushing this theme further, this visitor/filter will examine an object and report all uncompacted
Strings inside it:
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)
&& (parentobj.getClass () == 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 class
IObjectProfileNode profile = ObjectProfiler.profile (obj);
StringInspector si = new StringInspector ();
profile.traverse (si, si);
System.out.println ("wasted " + si.wasted () + " bytes (out of " +
profile.size () + ")");
For an example of using sizeof(), let's look at LinkedList() versus ArrayList(). This code:
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");
populates a list instance with 1,000 null references. The size of the resulting structure is thus the storage overhead of
the list implementation. For LinkedList and ArrayList collection implementations, sizeof() reports 20,040 and 4,112 bytes, respectively. Even though ArrayList grows its internal capacity ahead of its size (such that almost 50 percent of capacity could be wasted at any moment; this
is done to keep the amortized cost of insertion constant), its array-based design is far more memory efficient than LinkedList()'s doubly linked list implementation, which creates a 20-byte node to store each value. (This is not to say you should never
use LinkedLists: they guarantee constant unamortized insertion performance, among other things.)
Archived Discussions (Read only)