NOTE: The research and views presented below are mine and do not necessarily reflect those of my employer.
I sometimes see advice that object creation in Java is slow and should be avoided. The common counter argument is that it’s not slow, and even if it was slow it should be avoided only after causing a proven performance issue. I always liked the counter argument, to put it to the test I did experiments on how much time a modern JVM takes for object creation and subsequent garbage collection.
Java performance guides generally encourage writing code that maximizes readability and encapsulation, worrying about performance only once the code works and sufficient benchmarking is in place. The primary motivation for this advice is:
- Modern JVMs are built to optimize idiomatic Java code. Changing code for performance reasons may actually slow down program execution.
- The majority of execution time is taken up by a small minority of the lines of code. Identifying these lines and fixing the issues is much easier to do on clean, well factored code.
- Code is easier to write, maintain, and extend when it is optimized for readability. The initial code will almost always have sufficient performance, so it is a waste to performance test and rewrite the code.
In spite of the above, the advice of avoiding object creation persists. I speculate that this is in part because object creation is easy to spot in Java (see bikeshedding). To put numbers on the merits of avoiding object creation I ran tests using the Java Microbenchmark Harness (JMH).
Summary:
These tests were run on a 13″ retina MacBook pro (early 2015) with a 2.7 GHz Intel Core i5 processor and 16 GB 1867 MHz DDR3 memory, with a 4 GB JVM heap (see below for code and JVM details). These numbers will be different on different JVMs and hardware.
Operation | Nanoseconds per Operation |
---|---|
Creating an object that escapes method scope but immediately becomes unreferenced | 3.6 ns |
Creating an object whose reference is held in a collection for some time | 12.9 ns – ArrayList 127.5 ns – LinkedList |
Creating an object that never escapes method scope | 0 ns* *the compiler may inline the entire class |
The results show that creating a short lived object is quite cheap, even including the cost of garbage collection. Holding a longer lived reference to the object in a collection greatly increases the cost due to less efficient GC and the added memory overhead for the collection’s data structure (LinkedList is ~10x more expensive than ArrayList in this test). A control test showed that ArrayList creation itself was not responsible for the increase in time.
Watching JVM performance during the test with VisualVM, the ~10% GC time incurred by creating long lived objects is clear:
Conclusion:
The cost (in this specific test environment) of creating a short lived object, which most objects are, is only 3.6 ns. If all a sever did was create 1,000 objects per request it could handle a massive ~280,000 requests per second. It is highly unlikely that object creation will cause a noticeable performance impact compared to other actions needed to handle a request.
From the perspective of factoring code, let’s say we could trade 4 lines of code for creation of one object (due to simpler logic and more idiomatic code). Even using the slower number of 12.9 ns, we can reduce 50,000 lines of application code to 12,500 lines while only adding a fraction of a millisecond of total computation time (10,000 * 12.9 ns = 0.13 ms). The new code may even be faster due to enhanced opportunity for JVM optimizations. In any case if I needed to optimize 50,000 lines of code that had avoided object creation, the first thing I’d do is refactor the code to create objects where it made sense, and then optimize the cleaner, easier to modify code.
Methods:
The code used for these tests is available on GitHub (also useful: this JMH tutorial). Below are the code snippets corresponding to each of the situations above (LOOP_SIZE
was set to 10,000,000 and the results show the execution time for each method divided by LOOP_SIZE
).
- Creating an object that escapes method scope but immediately becomes unreferenced:
public Long escapee; @Benchmark public Long createShortLivedObjects() { for (int i = 0; i < LOOP_SIZE; i++) { // Need to set value, else compiler optimizes away the loop escapee = Long.valueOf(i); } return escapee; }
- Creating an object whose reference is held in a collection for some time
@Benchmark public List<long> createLongLivedObjectsArrayList() { List<long> result = new ArrayList<>(LOOP_SIZE); for (int i = 0; i < LOOP_SIZE; i++) { result.add(Long.valueOf(i)); } return result; } @Benchmark public List<long> createLongLivedObjectsLinkedList() { List<long> result = new LinkedList<>(); for (int i = 0; i < LOOP_SIZE; i++) { result.add(Long.valueOf(i)); } return result; } </long></long></long></long>
- Array list creation control (took 4.2 ns per element):
private static final Long ONE = 1L; @Benchmark public List<long> createListOneObject() { List<long> result = new ArrayList<>(LOOP_SIZE); for (int i = 0; i < LOOP_SIZE; i++) { result.add(ONE); } return result; } </long></long>
- Creating an object that never escapes method scope: See this post.
JVM Details:
$java -version java version "1.8.0_60" Java(TM) SE Runtime Environment (build 1.8.0_60-b27) Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)