This blog came about because I was looking into performance issue and profiling showed that a method that was copying objects was very slow. (NB: The fact that this method was called millions of times was an actual bug not the slowness of the copy routine.)
The method to copy object was performing field by field copy using
class.getDeclaredFeilds() method to obtain all fields of the class and then doing copy recursively for all super classes. Code below shows entire thing:
Profiling showed that this method spend 70% of it’s time in
Field#copy() method. As it turns out that
Class#getDeclaredFields() returns a copy of the
Field array on every call, ouch! BTW, the JavaDoc of
Class#getDeclaredFields() does not mention copy behavior at all.
List of approaches
Looking at the profiling results I started wondering what would be a better way to create a shallow copy of the object. Hence I needed to test performance of different approaches. For the simplicity I decided to look only on what is possibly with JDK 7 only (no fancy libraries) and came up with the following list:
- Clone object – implement
Cloneableinterface and publish
- Copy with copy constructor – copy fields from the source object directly in the constructor
- Copy via reflection (field by field)
getDeclaredFieds()as in original code
getDeclaredFields()cached, i.e. call it once and remember list based on the Class
- Default serialization – just implementing
- Custom serialization – implement Serialization and implement
writeObject()methods for reading and writing fields directly
- Default serialization – just implementing
Notes on implementation
Entire project with source code and test results is available on github (copy-object-benchmark.git).
Benchmarks are written using JMH framework from OpenJDK. For each class tested there is a method in the benchmark class that simply invokes
copy() method on the constant object representing the class. For example:
Every approach from the list above was benchmarked for 2 cases:
- Primitive fields
- Object fields
Because most of the approaches would incur significant overhead via boxing/unboxing.
For every kind of copy method there is a class that extends common super class (i.e.
BaseClass) and implements
copy() method. Since there are 2 use cases tests had to be duplicated in 2 different packages.
One last thing that is worth mentioning before I show the results is the handling of
MethodHandle#invokeExact() case. This method is very special when it gets to invocation. Here is an example:
As you can see in order to call
MethodHandle#invokeExact() it is necessary to match exactly arguments and return type of the method, otherwise such invocation will fail at runtime.
However it is not possible to know upfront all possible classes and return types if you want to write generic copy method. Therefore we need to adjust
MethodHandle so it can be invoked in a generic manner. This is done by adjusting
MethodType signatures of the
MethodHandle as shown below:
Basically this erases original information about declaration class and return types and instead uses
Object for both thus allowing calling
MethodHandle#invokeExact() providing instance of any class and expecting result as
I was running the benchmarks on my MacBook Pro laptop: OS X 10.9, 2.7 GHz Intel Core i7, 16 GB of RAM. Tests were performed on the following JDKs:
- 1.7.0_25 (
- 1.7.0_45 (
- JDK 8 build 112 (
Results are reported as throughput (operations/ms) with biggest numbers being the best. All charts are in logarithmic scale.
- JDK 1.7.0_25:
Raw results are available here results-1.7.0_25.txt.
So in this version of JDK the best 3 methods were:
- Copy constructor
MethodHandlesare very slow comparing to other approaches.
- JDK 1.7.0_45:
Raw results results-1.7.0_45.txt.
In the 1.7.0_45 release we see that
MethodHandle#invokeExact()got much faster (~5x times) to the point that it made it to top 3 copy methods (for primitives case). Also
MethodHandle#invokeWithArguments()got slower. But otherwise the rest remains the same.
- JDK 8 build 112:
Raw results results-1.8.0-ea-b112.txt.
JDK 8 build 112 brings significant performance improvements over JDK 7 results in two areas:
Most dramatic change was in the
MethodHandler#invoke()case which now is more than 120x times faster! The changes were done originally proposed by John Rose on a JDK mailing list and implemented as part of the JDK-8024761: JSR 292 improve performance of generic invocation issue.
Along the way
MethodHandle#invokeWithArguments()got faster as well.
- New JDK releases bring performance improvements
- New JDK versions bring new functionality that can be used in place of old approaches (e.g.
- JVM is extremely good at optimizing existing code, i.e. reflection is still faster than invoke dynamic (at least for Object case)
- Read JavaDoc but step through the code in the debugger and not just through your own code but also JDK code
- Write performance tests upfront to know whether selected approach meets performance requirements
- Run performance tests against new JDK versions to avoid/detect performance regressions
I’ve published second blog post Clone vs copy constructor – a closer look in which I analyze performance difference of clone and copy constructor approaches.