|
|
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 6 of 7
Since the introduction of JDK 1.3, setting final fields via reflection is no longer possible (see Note 1 in Resources); so, this approach resembles Approach 1 because it can't handle final fields. Note also that inner classes cannot have no-arg constructors by definition (see Note 2 in Resources), so this approach will not work for them either.
Coupled with the no-arg constructor requirement, this approach restricts the type of classes it can handle. But you would
be surprised how far it can go. The full implementation adds a few useful features. While traversing the object graph rooted at the source object, it keeps an internal objMap parameter that maps values in source object graphs to their respective clones in the cloned graphs. This restores the ability
to preserve object graphs that I had in Approach 3. Also, the metadataMap parameter caches class metadata for all classes that it encounters while cloning an object and improves performance by avoiding
slow reflection. The relevant data structures are scoped to a single call to clone(), and the overall idea is very similar to Java serialization revamped to just do object cloning. Similar to the previous section,
a whole hierarchy of suitable classes can be made cloneable by equipping the base class with one generic method:
public Object clone ()
{
return ReflectiveClone.clone (this);
}
What is this method's performance? Rerunning the test with the REFLECTION branch selected produces:
clone implementation: reflection method duration: 0.537 ms
This is roughly five times faster than straightforward serialization—not too bad for another generic approach. In terms of its performance and capabilities, it represents a compromise between the other three solutions. It can work very well for JavaBean-like classes and other types that usually do not have final fields.
Measuring memory overhead is more difficult than measuring performance. It should be obvious that the first two approaches shine in this area, as they instantiate only the data that will populate the cloned fields.
Cloning via serialization has an extra drawback that may have escaped your attention above. Even though serializing an object
preserves the structure of the object graph rooted at that instance, immutable values will get duplicated across disjoint
calls to clone(). As an example, you can verify for yourself that
TestClass obj = new TestClass ("dummy");
System.out.println (obj.m_string == ((TestClass) obj.clone ()).m_string);
Strings. Approaches 1 and 2 are completely free from this problem, and Approach 3 is mostly free from it.A quick and dirty proof of these observations can be seen by changing the body of Main.main() to keep the clones in memory and track the object count when a given heap size is reached:
int count = 0;
List list = new LinkedList ();
try
{
while (true)
{
list.add (obj.clone ());
++ count;
}
}
catch (Throwable t)
{
System.out.println ("count = " + count);
}
Run this in a JVM with a -Xmx8m setting and you will see something similar to this: