Make use of JVM and CPU performance counters. They're amazing for discovery and understanding of performance matters.
Quick googling found these:
https://zeroproductionincidents.wordpress.com/tag/jvm-perfor...
http://www.peternier.com/projects/overseer/overseer.php
Make sure you also test in actual application context. Microbenchmarking runs into risk of consuming shared CPU resources (such as L1 cache) and exhausting them when running an actual application.
In other words, something that is fast in a microbenchmark can perform badly in an actual application.
Other than that, one thing no one mentioned is to arrange data in cache-friendly format. Sometimes it might mean ugly things like arrays of primitive types instead of arrays of objects...
False sharing is another real performance killer in parallel code.
IMHO, writing high performance Java code is a lot harder than high performance C/C++.