In software development, the programming language Java was historically considered slower than the fastest third-generation typed languages such as C and C++. In contrast to those languages, Java compiles by default to a Java Virtual Machine (JVM) with operations distinct from those of the actual computer hardware. Early JVM implementations were interpreters; they simulated the virtual operations one-by-one rather than translating them into machine code for direct hardware execution.

Since the late 1990s, the execution speed of Java programs improved significantly via introduction of just-in-time compilation (JIT) (in 1997 for Java 1.1), the addition of language features supporting better code analysis, and optimizations in the JVM (such as HotSpot becoming the default for Sun's JVM in 2000). Sophisticated garbage collection strategies were also an area of improvement. Hardware execution of Java bytecode, such as that offered by ARM's Jazelle, was explored but not deployed.

The performance of a Java bytecode compiled Java program depends on how optimally its given tasks are managed by the host Java virtual machine (JVM), and how well the JVM exploits the features of the computer hardware and operating system (OS) in doing so. Thus, any Java performance test or comparison has to always report the version, vendor, OS and hardware architecture of the used JVM. In a similar manner, the performance of the equivalent natively compiled program will depend on the quality of its generated machine code, so the test or comparison also has to report the name, version and vendor of the used compiler, and its activated compiler optimization directives.

Virtual machine optimization methods Many optimizations have improved the performance of the JVM over time. However, although Java was often the first virtual machine to implement them successfully, they have often been used in other similar platforms as well.

Just-in-time compiling

Early JVMs always interpreted Java bytecodes. This had a large performance penalty of between a factor 10 and 20 for Java versus C in average applications. To combat this, a just-in-time (JIT) compiler was introduced into Java 1.1. Due to the high cost of compiling, an added system called HotSpot was introduced in Java 1.2 and was made the default in Java 1.3. Using this framework, the Java virtual machine continually analyses program performance for hot spots which are executed frequently or repeatedly. These are then targeted for optimizing, leading to high performance execution with a minimum of overhead for less performance-critical code. Some benchmarks show a 10-fold speed gain by this means.This article shows that the performance gain between interpreted mode and Hotspot amounts to more than a factor of 10. However, due to time constraints, the compiler cannot fully optimize the program, and thus the resulting program is slower than native code alternatives.Numeric performance in C, C# and Java Algorithmic Performance Comparison Between C, C++, Java and C# Programming Languages

Adaptive optimizing

Adaptive optimizing is a method in computer science that performs dynamic recompilation of parts of a program based on the current execution profile. With a simple implementation, an adaptive optimizer may simply make a trade-off between just-in-time compiling and interpreting instructions. At another level, adaptive optimizing may exploit local data conditions to optimize away branches and use inline expansion.

A Java virtual machine like HotSpot can also deoptimize code formerly JITed. This allows performing aggressive (and potentially unsafe) optimizations, while still being able to later deoptimize the code and fall back to a safe path.

Garbage collection

The 1.0 and 1.1 Java virtual machines (JVMs) used a mark-sweep collector, which could fragment the heap after a garbage collection. Starting with Java 1.2, the JVMs changed to a generational collector, which has a much better defragmentation behaviour.IBM DeveloperWorks Library Modern JVMs use a variety of methods that have further improved garbage collection performance.For example, the duration of pauses is less noticeable now. See for example this clone of Quake II written in Java: Jake2.

Other optimizing methods

Compressed Oops Compressed Oops allow Java 5.0+ to address up to 32 GB of heap with 32-bit references. Java does not support access to individual bytes, only objects which are 8-byte aligned by default. Because of this, the lowest 3 bits of a heap reference will always be 0. By lowering the resolution of 32-bit references to 8 byte blocks, the addressable space can be increased to 32 GB. This significantly reduces memory use compared to using 64-bit references as Java uses references much more than some languages like C++. Java 8 supports larger alignments such as 16-byte alignment to support up to 64 GB with 32-bit references.

Split bytecode verification Before executing a class, the Sun JVM verifies its Java bytecodes (see bytecode verifier). This verification is performed lazily: classes' bytecodes are only loaded and verified when the specific class is loaded and prepared for use, and not at the beginning of the program. However, as the Java class libraries are also regular Java classes, they must also be loaded when they are used, which means that the start-up time of a Java program is often longer than for C++ programs, for example.

A method named split-time verification, first introduced in the Java Platform, Micro Edition (J2ME), is used in the JVM since Java version 6. It splits the verification of Java bytecode in two phases: Design-time – when compiling a class from source to bytecode Runtime – when loading a class.

In practice this method works by capturing knowledge that the Java compiler has of class flow and annotating the compiled method bytecodes with a synopsis of the class flow information. This does not make runtime verification appreciably less complex, but does allow some shortcuts.

Escape analysis and lock coarsening

Java is able to manage multithreading at the language level. Multithreading allows programs to perform multiple processes concurrently, thus improving the performance for programs running on computer systems with multiple processors or cores. Also, a multithreaded application can remain responsive to input, even while performing long running tasks.

However, programs that use multithreading need to take extra care of objects shared between threads, locking access to shared methods or blocks when they are used by one of the threads. Locking a block or an object is a time-consuming operation due to the nature of the underlying operating system-level operation involved (see concurrency control and lock granularity).

As the Java library does not know which methods will be used by more than one thread, the standard library always locks blocks when needed in a multithreaded environment.

Before Java 6, the virtual machine always locked objects and blocks when asked to by the program, even if there was no risk of an object being modified by two different threads at once. For example, in this case, a local was locked before each of the add operations to ensure that it would not be modified by other threads ( is synchronized), but because it is strictly local to the method this is needless: public String getNames() { final Vector<String> v = new Vector<>(); v.add("Me"); v.add("You"); v.add("Her"); return v.toString(); } Starting with Java 6, code blocks and objects are locked only when needed, so in the above case, the virtual machine would not lock the Vector object at all.

Since version 6u23, Java includes support for escape analysis.

Register allocation improvements Before Java 6, allocation of registers was very primitive in the client virtual machine (they did not live across blocks), which was a problem in CPU designs which had fewer processor registers available, as in x86s. If there are no more registers available for an operation, the compiler must copy from register to memory (or memory to register), which takes time (registers are significantly faster to access). However, the server virtual machine used a color-graph allocator and did not have this problem.

An optimization of register allocation was introduced in Sun's JDK 6;Bug report: new register allocator, fixed in Mustang (JDK 6) b59 it was then possible to use the same registers across blocks (when applicable), reducing accesses to the memory. This led to a reported performance gain of about 60% in some benchmarks.Mustang's HotSpot Client gets 58% faster! in Osvaldo Pinali Doederlein's Blog at java.net

Class data sharing Class data sharing (called CDS by Sun) is a mechanism which reduces the startup time for Java applications, and also reduces memory footprint. When the JRE is installed, the installer loads a set of classes from the system JAR file (the JAR file holding all the Java class library, called rt.jar) into a private internal representation, and dumps that representation to a file, called a "shared archive". During subsequent JVM invocations, this shared archive is memory-mapped in, saving the cost of loading those classes and allowing much of the JVM's metadata for these classes to be shared among multiple JVM processes.Class Data Sharing at java.sun.com

The corresponding improvement in start-up time is more obvious for small programs.Class Data Sharing in JDK 1.5.0 in Java Buzz Forum at artima developer

History of performance improvements

Apart from the improvements listed here, each release of Java introduced many performance improvements in the JVM and Java application programming interface (API).

JDK 1.1.6: First just-in-time compilation (Symantec's JIT-compiler)

J2SE 1.2: Use of a generational collector.

J2SE 1.3: Just-in-time compiling by HotSpot.

J2SE 1.4: See here, for a Sun overview of performance improvements between 1.3 and 1.4 versions.

Java SE 5.0: Class data sharingSun overview of performance improvements between 1.4 and 5.0 versions.

Java SE 6: Split bytecode verification Escape analysis and lock coarsening Register allocation improvements

Other improvements: Java OpenGL Java 2D pipeline speed improvementsSTR-Crazier: Performance Improvements in Mustang in Chris Campbell's Blog at java.net Java 2D performance also improved significantly in Java 6See here for a benchmark showing a performance boost of about 60% from Java 5.0 to 6 for the application JFreeChart

See also 'Sun overview of performance improvements between Java 5 and Java 6'.Java SE 6 Performance White Paper at http://java.sun.com

Java SE 6 Update 10 Java Quick Starter reduces application start-up time by preloading part of JRE data at OS startup on disk cache. Parts of the platform needed to execute an application accessed from the web when JRE is not installed are now downloaded first. The full JRE is 12 MB, a typical Swing application only needs to download 4 MB to start. The remaining parts are then downloaded in the background. Graphics performance on Windows improved by extensively using Direct3D by default, and use shaders on graphics processing unit (GPU) to accelerate complex Java 2D operations.

Java 7 Several performance improvements have been released for Java 7: Future performance improvements are planned for an update of Java 6 or Java 7: Provide JVM support for dynamic programming languages, following the prototyping work currently done on the Da Vinci Machine (Multi Language Virtual Machine), Enhance the existing concurrency library by managing parallel computing on multi-core processors, Allow the JVM to use both the client and server JIT compilers in the same session with a method called tiered compiling: The client would be used at startup (because it is good at startup and for small applications), The server would be used for long-term running of the application (because it outperforms the client compiler for this).