Yes I know, this has been a “hobby-horse” of mine since ColdFusion migrated to Java back in 2000-2001. The bottom line is still the same, all JVM’s need tuning and there are not actually a plethora of companies addressing this. In fact there seem to be fewer such companies today and some charge over $1,200.00 per anum to do so. So I decided to create a little series to help with this. With this being said; off we jolly-well go and if you want more detail straight away, take a look here.
The image above is one I created years ago to simply show where CF sits in the JVM Stack. Threads are a really critical issue in this scenario and we covered thread management in this previous article.
The Impossibility Of Correctly Tuned JVM’s (Out Of The Box):
If you have an Android Smartphone, it is more than likely you will have a Java Virtual Machine (JVM) in some app on there.
In Wall Street, High Frequency Trading hardware, Java is often the platform used and once again uses a JVM. These computer systems are huge and powerful.
Twitter ran on Java for some time.
Anyone going to Sun Microsystems (historically) or Oracle (currently) and downloading a JVM, with default settings will not get a JVM that is correctly set for any and all applications because it is impossible to have such a generic option. This a good article on Java in Sun Microsystems and then Oracle.
Therefore, any and all Java applications which have not been tuned will perform in a sub-par manner; (since 2001 ColdFusion has been running in Java). In all my career, going back to Allaire Corporation in 1999, there has never been any client I have helped that had an optimally running JVM. That is now in a span of over 25 years.
There is another important issue here, after we moved into Cloud-Based infrastructure, like AWS, Microsoft Azure and Google Cloud, we typically pay for things like bandwidth and throughput like electricity, by how much we consume. So inefficiency costs clients money, directly.
Java Virtual Machine (JVM) Architecture:
There is a slogan which accompanies the Java programming language “Write Once Run Anywhere”. Java can run on pretty much all mainstream operating systems, the ones that we typically incur are Microsoft Windows, Linux and Unix (Apple systems typically run Linux BSD as the core operating system).
All operating systems and therefore software applications rely on managing memory and threads. The threads eventually run on the CPU or GPU core using needed memory often in a cache. This is an article I created on these aspects.
Why This is Important:
Platform Independence:
Java’s “write once, run anywhere” philosophy is achieved through the JVM, which provides a consistent execution environment regardless of the operating system or hardware.
Thread and Memory Management:
The JVM creates and manages its own threads and memory regions (e.g., heap and metaspace) within the resources allocated by the operating system.
It doesn’t directly interact with hardware like CPUs and GPUs; instead, it uses the OS's APIs to schedule threads and allocate memory.
JVM Tuning:
Since the JVM operates as an abstraction layer, efficient tuning (e.g., heap size, garbage collector selection, thread pool size) is essential to avoid bottlenecks and maximize application performance.
Importance of Abstraction:
This abstraction allows Java to run on a wide variety of devices, from servers to IoT devices, but requires careful tuning to bridge the gap between the JVM’s needs and the OS's capabilities.
This site has some in-depth articles on the aspects of software code abstraction and related topics.
Java Virtual Machine (JVM) Versions:
This can be useful to know as we could encounter any of these JVM’s, typically with ColdFusion I have not encountered any earlier versions than Sun Microsystems JDK 1.3. Another aspect here JVM’s can have Multiple Garbage Collector choices.
Here's a detailed history of JVM versions, their release years, and the default Garbage Collector (GC) where known. This includes the Sun Microsystems era up to Oracle's stewardship.
Sun Microsystems Era
Version - Year Released - Default Garbage Collector - Key Features
JDK 1.0 1996 Mark-Sweep-Compact Initial release; basic JVM capabilities.
JDK 1.1 1997 Mark-Sweep-Compact Inner classes, JDBC, JavaBeans.
JDK 1.2 1998 Serial GC Introduction of the Java Collections Framework.
JDK 1.3 2000 Serial GC HotSpot JVM introduced.
JDK 1.4 2002 Parallel GC NIO, Assertions, Logging.
Oracle Era
Version Year Released Default Garbage Collector Key Features
JDK 5.0 2004 Parallel GC Generics, Annotations, java.util.concurrent.
JDK 6 2006 Parallel GC Enhanced scripting, Compiler API, JDBC 4.0.
JDK 7 2011 Parallel GC Fork/Join Framework, NIO.2, G1 GC introduced.
JDK 8 2014 Parallel GC Lambda expressions, Streams API, default G1 GC.
JDK 9 2017 G1 GC Modular System (Project Jigsaw), JShell.
JDK 10 2018 G1 GC Local variable type inference (var).
JDK 11 2018 G1 GC Long-term support (LTS), HTTP Client API.
JDK 12 2019 G1 GC Shenandoah and ZGC introduced as experimental GCs.
JDK 13 2019 G1 GC Text blocks, dynamic CDS archives.
JDK 14 2020 G1 GC Switch expressions, ZGC improvements.
JDK 15 2020 G1 GC Sealed classes (preview), hidden classes.
JDK 16 2021 G1 GC Vector API (Incubator), records.
JDK 17 2021 G1 GC Long-term support (LTS), sealed classes.
JDK 18 2022 G1 GC Simple web server, UTF-8 default charset.
JDK 19 2022 G1 GC Virtual threads (preview)*, pattern matching updates.
JDK 20 2023 G1 GC Record patterns, further virtual thread enhancements*.
JDK 21 2023 G1 GC LTS release, Generational ZGC introduced.
*Note the introduction of virtual threads; here is an explanation overview of this feature.
What are Virtual Threads?
Definition: Virtual Threads are lightweight threads managed by the Java Virtual Machine (JVM), not directly tied to OS threads.
Key Goal: To make thread creation and management inexpensive, enabling applications to have millions of concurrent threads.
Key Features
Lightweight:
Virtual Threads are much lighter than traditional (platform) threads.
They consume fewer resources, as they are not bound to a specific OS thread for their entire lifecycle.
Non-blocking Operations:
Blocking operations (e.g., I/O) do not block the underlying OS thread. Instead, the JVM can manage them efficiently, allowing other virtual threads to execute on the same OS thread.
High Scalability:
Virtual Threads allow applications to handle large numbers of concurrent tasks with minimal overhead, making them ideal for server-side applications.
Ease of Use:
The API for virtual threads integrates seamlessly with the existing
java.lang.Thread
class, preserving familiarity for developers.
This is just an overview and we intend to do a detailed article as to how we can take advantage of virtual threads from ColdFusion applications.
Garbage Collector Evolution
Serial GC: Early versions relied on this simple, stop-the-world collector.
Parallel GC (Throughput Collector): Became the default starting with JDK 1.4, offering better performance in multi-threaded environments.
G1 GC (Garbage-First): Introduced in JDK 7 and became the default in JDK 9. It balances throughput and pause time predictability.
ZGC (Z Garbage Collector): Introduced in JDK 12 for ultra-low pause times.
Shenandoah GC: Also introduced as experimental in JDK 12, focuses on low-latency use cases.
Main JVM Arguments Passed In At Start Time:
Typically set in the jvm.config file.
Yes, you can pass various arguments to the Java Virtual Machine (JVM) at startup to configure its behavior, optimize performance, and manage resources. These arguments are typically categorized into standard, non-standard, and advanced options.
Standard Options: These are universally supported across all JVM implementations. You can view them by running java in your command line without additional parameters. Common standard options include:
-version: Displays the version of the JVM.
-help: Lists all standard options.
Non-Standard Options: These options, prefixed with -X, provide additional control over the JVM's behavior. To see a list of non-standard options, execute java -X in your command line. Examples include:
-Xms<size>: Sets the initial heap size.
-Xmx<size>: Defines the maximum heap size.
-Xss<size>: Specifies the thread stack size.
Advanced Options: Prefixed with -XX:, these options offer fine-grained control over JVM internals. They are often used for performance tuning and debugging. Some commonly used advanced options are:
-XX:+UseG1GC: Enables the G1 garbage collector.
-XX:MaxPermSize=<size>: Sets the maximum size for the permanent generation (applicable in Java versions prior to 8).
-XX:+HeapDumpOnOutOfMemoryError: Generates a heap dump when an OutOfMemoryError occurs.
For a comprehensive list of JVM parameters and their descriptions, you can refer to resources like Baeldung's guide on JVM parameters. There is a more complete list here.
Additionally, the OpenJDK documentation provides detailed information on HotSpot-specific options. This is a good list.
It's important to note that while these resources provide extensive information, the availability and behavior of certain JVM arguments can vary between different Java versions and implementations. Always refer to the documentation corresponding to the specific JVM version you're using to ensure compatibility and optimal configuration.
When configuring JVM arguments, it's crucial to understand their impact on your application's performance and stability. Improper use of certain parameters can lead to suboptimal performance or unexpected behavior. Therefore, it's advisable to test configurations in a controlled environment before deploying them to production systems.
Conclusion:
All JVM’s Need Tuning
An Out Of Memory Error (OOM) Does Not Always Cause Thread Exhaustion & System Crash.
Running Out Of Threads With No Recovery Will Cause An Out Of Memory Error (OOM) & Eventual Application & Possibly System Crash.
All Of These Issues Will Cost Clients Money & Possibly Lost Customers For Our Clients.
Thanks as always for reading this, there will be more where this came from; soon.