Will a LinkedList be faster? Should I swap the `for each` with an `iterator`? Should this `ArrayList` be an `Array`? This article came to be in response to an optimization so malevolent it has permanently etched itself into my memory. Before going head-on into Java and the ways to tackle interference, either from the garbage collector or from context switching, let's first glance over the fundamentals of writing code for your future self. Premature optimization is the root of all evil. You've heard it before; premature optimization is the root of all evil. Well, sometimes. When writing software, I'm a firm believer in being: as ; you should try to narrate intentions as if you were writing a story. descriptive as possible as ; which means that you should know the fundamentals of the language and apply them accordingly. optimal as possible As Descriptive as Possible Your code should speak intention, and a lot of it pertains to the way you name methods and variables. int[10] array1; // bad int[10] numItems; // better int[10] backPackItems; // great Just by the variable name, you can already infer functionality. While is abstract, tells you a lot about expected behavior. numItems backPackItems Or say you have this method: List<Countries> visitedCountries() { if(noCountryVisitedYet) return new ArrayList<>(0); } // (...) return listOfVisitedCountries; } As far as code goes, this looks more or less ok. Can we do better? We definitely can! List<Countries> visitedCountries() { if(noCountryVisitedYet) return Collections.emptyList(); } // (...) return listOfVisitedCountries; } Reading is much more descriptive than Collections.emptyList() new ArrayList<>(0); Imagine you're reading the above code for the first time and stumble on the that checks if the user has actually visited countries. Also, imagine this is buried in a lengthy class, reading is definitely more descriptive than , you're also making sure it's immutable making sure client code can't modify it. guard clause Collections.emptyList() new ArrayList<>(0) As Optimal as Possible Know your language, and use it accordingly. If you need a , there's no need to wrap it in a object. The same goes for using a if all you actually need is an . double Double List Array Know that you should concatenate Strings using or if you're sharing state between threads: StringBuilder StringBuffer // don't do this String votesByCounty = ""; for (County county : counties) { votesByCounty += county.toString(); } // do this instead StringBuilder votesByCounty = new StringBuilder(); for (County county : counties) { votesByCounty.append(county.toString()); } Know how to index your database. Anticipate bottlenecks and cache accordingly. All the above are optimizations. They are the kind of optimizations that you should be aware of and implement as first citizens. How Do You Kill It First? I'll never forget about a hack I read a couple of years ago. Truth be said, the author backtracked quickly, but it goes to show how a lot of evil can spur from good intentions. // do not do this, ever! int i = 0; while (i<10000000) { // business logic if (i % 3000 == 0) { //prevent long gc try { Thread.sleep(0); } catch (Ignored e) { } } } A garbage collector hack from hell! You can read more on why and how the above code works and, while the exploit is definitely interesting, this is one of those things you should ever do. in the original article never Works by side effects, has no purpose in this block Thread.sleep(0) Works by exploiting a deficiency of code downstream For anyone inheriting this code, it's obscure and magical Only start forging something a bit more involved if, after writing with all the , you've hit a bottleneck. But steer away from concoctions as the above. default optimizations the language provides How to Tackle Garbage Collector? That If after all's done, the Garbage Collector is still the piece that's offering resistance, these are some of the things you may try: If your service is so latency-sensitive that you can't allow for GC, run with . "Epsilon GC" and avoid GC altogether -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC This will obviously grow your memory until you get an OOM exception, so either it's a short-lived scenario or your program is optimized not to create objects If your service is somewhat latency sensitive, but , run GC1 and feed it something like (default is 250ms) the allowed tolerance permits some leeway -XX:MaxGCPauseTimeMillis=100 , say one of them calls or which are stop-the-world garbage collectors, you can override offending behavior by running with If the issue spurs from external libraries System.gc() Runtime.getRuntime().gc() -XX:+DisableExplicitGC If you're running on a JVM above 11, do try the , performance improvements are monumental! . You may also want to check this . Z Garbage Collector (ZGC) -XX:+UnlockExperimentalVMOptions -XX:+UseZGC JDK 21 GC benchmark VERSION START VERSION END DEFAULT GC Java 1 Java 4 Serial Garbage Collector Java 5 Java 8 Parallel Garbage Collector Java 9 ongoing G1 Garbage Collector Note 1: since Java 15, is , but you still have to explicitly activate it with . ZGC production-ready -XX:+UseZGC Note 2: The VM considers machines as server-class if the VM detects more than two processors and a heap size larger or equal to 1792 MB. If not server-class, it . will default to the Serial GC In essence, opt for GC tuning when it's clear that the application's performance constraints are directly tied to garbage collection behavior and you have the necessary expertise to make informed adjustments. Otherwise, trust the JVM's default settings and focus on optimizing application-level code. - you'll want to read the u/shiphe full comment Other Relevant Libraries You May Want to Explore: Java Microbenchmark Harness (JMH) If you're out of feeling without any real benchmarking, you're doing yourself a disservice. JMH is the Java library to test your algorithms' performance. Use it. optimizing de facto Java-Thread-Affinity Pinning a process to a specific core may improve cache hits. It will depend on the underlying hardware and how your routine is dealing with data. Nonetheless, this library makes it so easy to implement that, if a CPU-intensive method is dragging you, you'll want to test it. LMAX Disruptor This is one of those libraries that, even if you don't need it, you'll want to study. The idea is to allow for ultra-low latency concurrency. But the way it's implemented, from to the brings a lot of new concepts. I still remember when I first discovered it, seven years ago, pulling an all-nighter to digest it. mechanical sympathy ring buffer, Netflix jvmquake The premise of is that when things go sideways with the JVM, you want it to die and not hang. A couple of years ago, I was running simulations on an HTCondor cluster that was on tight memory constraints, and sometimes, jobs would get stuck due to "out of memory" errors. jvmquake This library forces the JVM to die, allowing you to deal with the actual error. In this specific case, HTCondor would auto-re-schedule the job. Final Thoughts The code that made me write this post? I've written way worse. I still do. The best we can hope for is to continuously mess up less. I'm expecting to be disgruntled looking at my own code a few years down the road. And that's a good sign. You can find this and other articles at wasteofserver.com Edits & Thank You: to for catching the mutability error in and the detailed explanation. FlorianSchaetz visitedCountries() to and for explaining that , you get Serial GC u/brunocborges u/BikingSquirrel on lower-end machines to for taking the time to better explain when you should mess with the GC and u/shiphe when you shouldn't to for putting me on the right track regarding what practice u/tomwhoiscontrary should be considered standard to (again) for providing the link to the JDK 21 garbage collector benchmark u/BikingSquirrel Also published on wasteofserver.com