Showing posts with label JVM. Show all posts
Showing posts with label JVM. Show all posts

Friday, May 31, 2024

Java Virtual Machine: Comprehensive Guide

Java Virtual Machine: Comprehensive Guide

The Java Virtual Machine (JVM) is a cornerstone of the Java programming language, pivotal in enabling its platform-independent capabilities. This comprehensive guide delves into the intricacies of the JVM, shedding light on its architecture, functionalities, and its role in modern software development.

Understanding the Java Virtual Machine


The Java Virtual Machine, or JVM, is a crucial component of the Java Runtime Environment (JRE). It abstracts the underlying hardware and operating system, allowing Java applications to run on any device equipped with the JRE. The JVM achieves this by converting Java bytecode into machine-specific instructions.

Architecture of the Java Virtual Machine


Class Loader Subsystem

The Class Loader Subsystem is responsible for loading class files into the JVM. It operates in three main phases:

  • Loading: The JVM loads class files from the file system, network, or other sources.
  • Linking: This phase involves verifying, preparing, and resolving classes.
    • Verification: Ensures the bytecode adheres to the JVM's specifications.
    • Preparation: Allocates memory for class variables and initializes them to default values.
    • Resolution: Converts symbolic references into direct references.
  • Initialization: Executes static initializers and the class's static blocks.

Runtime Data Areas

The JVM's runtime data areas are pivotal for executing Java applications. These include:

  • Method Area: Stores class structures, including runtime constant pool, field and method data, and code for methods.
  • Heap: The runtime data area from which memory for all class instances and arrays is allocated.
  • Java Stacks: Each thread has a private JVM stack, created at the same time as the thread. A stack stores frames, holds local variables, and partial results.
  • PC Register: Each JVM thread has its own Program Counter (PC) register. It holds the address of the currently executing JVM instruction.
  • Native Method Stack: Contains all native methods used in the application.

Execution Engine

The execution engine interprets the bytecode. It consists of:

  • Interpreter: Reads and executes bytecode instructions one by one.
  • Just-In-Time (JIT) Compiler: Improves performance by compiling bytecode into native machine code at runtime.
  • Garbage Collector (GC): Automates memory management by reclaiming memory used by objects no longer in use.

Java Native Interface (JNI)

JNI allows Java code running in the JVM to call and be called by native applications and libraries written in other languages like C or C++. This interface ensures that Java applications can leverage platform-specific capabilities and optimize performance-critical sections.

Key Features of the Java Virtual Machine


Platform Independence

The JVM allows Java to be a "write once, run anywhere" language. Java programs, compiled into bytecode, can run on any system with a compatible JVM, ensuring broad compatibility and flexibility.

Automatic Memory Management

The JVM includes an automatic garbage collector that manages memory allocation and deallocation, reducing memory leaks and programmer overhead associated with manual memory management.

Robust and Secure Execution

The JVM performs various runtime checks, which enhances the robustness and security of Java applications. Bytecode verification ensures that the code adheres to JVM specifications, preventing malicious code execution.

Thread Management

The JVM provides built-in support for multithreading at the language level. Java threads are managed by the JVM, which provides an efficient, scalable platform for concurrent applications.

Garbage Collection in the JVM


Garbage collection is a form of automatic memory management. The JVM’s garbage collector is responsible for identifying and disposing of objects that are no longer needed by the application. The JVM uses several algorithms for garbage collection, including:

  • Serial Garbage Collector: Suitable for single-threaded environments.
  • Parallel Garbage Collector: Uses multiple threads to manage heap space, ideal for applications running on multi-core processors.
  • CMS (Concurrent Mark-Sweep) Garbage Collector: Designed for applications that require shorter garbage collection pauses.
  • G1 (Garbage-First) Garbage Collector: Balances high throughput with low pause times, suitable for large applications.

Performance Optimization Techniques


Just-In-Time Compilation

The JIT compiler plays a vital role in optimizing performance. By compiling bytecode into native machine code at runtime, the JIT compiler reduces the execution time significantly.

Escape Analysis

Escape analysis determines the scope of object usage. If an object does not escape a method or thread, the JVM can allocate it on the stack instead of the heap, reducing garbage collection overhead.

Inline Expansion

This optimization technique involves replacing a method call with the method body itself. Inline expansion reduces the overhead associated with method invocation and can significantly improve execution speed.

Adaptive Optimization

The JVM monitors application performance and adapts its optimization strategies dynamically. This involves collecting runtime statistics and using them to make informed decisions about when and how to optimize code.

The Future of the Java Virtual Machine


The JVM continues to evolve, integrating new features and optimizations to meet the demands of modern software development. Upcoming enhancements include:

  • Project Loom: Aiming to simplify concurrent programming with lightweight, user-mode threads (fibers).
  • Project Valhalla: Introducing value types to improve memory layout and reduce heap fragmentation.
  • Project Panama: Enhancing the connection between Java and native code to improve performance and flexibility.

Conclusion

The Java Virtual Machine is a critical component of the Java ecosystem, providing a platform-independent execution environment with robust security, automatic memory management, and high performance. As it evolves, the JVM will continue to support the development of scalable, efficient, and secure applications.

Monday, April 22, 2024

Unleashing the Power of Oracle JVM: A Comprehensive Feature Guide

Unleashing the Power of Oracle JVM: A Comprehensive Feature Guide

Introduction to Oracle JVM


At the heart of Java's versatility and performance lies the Java Virtual Machine (JVM). Among the myriad JVM implementations, Oracle JVM stands out as a powerhouse, offering a plethora of features and functionalities that empower developers to build robust, scalable, and efficient Java applications. In this comprehensive guide, we delve deep into the rich feature set of Oracle JVM, unlocking its full potential and shedding light on how it can elevate your Java development endeavors.

Garbage Collection Optimization


Enhanced Garbage Collection Algorithms

Oracle JVM boasts a sophisticated garbage collection mechanism designed to optimize memory utilization and enhance application performance. Leveraging state-of-the-art algorithms such as G1 Garbage Collector, Parallel GC, and Concurrent Mark-Sweep (CMS), Oracle JVM effectively manages memory allocation and deallocation, minimizing pauses and reducing the risk of memory leaks.

Tuning Options for Performance Fine-Tuning

With Oracle JVM, developers have access to an array of tuning options to fine-tune garbage collection behavior according to specific application requirements. From adjusting heap sizes and collector types to configuring generation sizes and collection intervals, Oracle JVM empowers developers to optimize garbage collection performance and achieve optimal throughput and latency for their Java applications.

Just-In-Time (JIT) Compilation


Dynamic Compilation for Performance Enhancement

Oracle JVM incorporates a sophisticated Just-In-Time (JIT) compiler that dynamically translates Java bytecode into native machine code at runtime, thereby optimizing code execution and enhancing overall application performance. By identifying hotspots and selectively compiling frequently executed code segments into highly efficient native instructions, Oracle JVM accelerates application startup time and improves runtime performance significantly.

Profile-Based Optimization Techniques

Driven by advanced profiling techniques, Oracle JVM's JIT compiler identifies performance bottlenecks and applies targeted optimizations to critical code paths, ensuring maximum throughput and responsiveness. By adapting compilation strategies based on runtime execution patterns and application behavior, Oracle JVM continuously fine-tunes performance to deliver optimal results across diverse workloads and use cases.

Advanced Monitoring and Diagnostics


Comprehensive Monitoring Tools

Oracle JVM provides developers with a suite of comprehensive monitoring tools, including Java Mission Control and Java Flight Recorder, enabling real-time visibility into JVM internals and application behavior. With detailed insights into memory usage, thread activity, garbage collection behavior, and CPU utilization, developers can diagnose performance issues, identify bottlenecks, and optimize application performance with precision.

Seamless Integration with Monitoring Frameworks

Facilitating seamless integration with popular monitoring frameworks such as Prometheus and Grafana, Oracle JVM enables developers to leverage a unified monitoring ecosystem for holistic observability and troubleshooting. By exporting rich telemetry data in standard formats, Oracle JVM simplifies monitoring and analysis, empowering developers to gain actionable insights and drive continuous performance improvements.

Enhanced Security Features


Robust Security Architecture

Oracle JVM incorporates a robust security architecture with built-in safeguards to protect against common security threats and vulnerabilities. Leveraging features such as Java Security Manager, Classloader Isolation, and Secure Execution Environment (SEE), Oracle JVM provides a fortified runtime environment that mitigates risks associated with malicious code execution, unauthorized access, and data breaches.

Regular Security Updates and Patching

Committed to ensuring the highest standards of security, Oracle delivers regular updates and patches to address emerging threats and vulnerabilities in Oracle JVM. By staying abreast of the latest security best practices and industry standards, Oracle JVM helps organizations maintain a secure and resilient Java runtime environment, safeguarding critical assets and preserving data integrity.

Conclusion

In conclusion, Oracle JVM stands as a pinnacle of excellence in the realm of Java Virtual Machines, offering an unparalleled array of features and capabilities to drive innovation and efficiency in Java development. From advanced garbage collection optimizations and dynamic JIT compilation to comprehensive monitoring tools and robust security features, Oracle JVM empowers developers to unleash the full potential of Java applications with confidence and reliability.

Wednesday, April 10, 2024

Unraveling the Oracle JVM: A Comprehensive Guide

Unraveling the Oracle JVM: A Comprehensive Guide

In the realm of Java programming, the Oracle JVM (Java Virtual Machine) stands as a cornerstone, wielding immense power and influence over the execution of Java applications. As developers navigate the intricate landscape of Java development, understanding the nuances of Oracle JVM becomes paramount. In this comprehensive guide, we delve deep into the inner workings of Oracle JVM, shedding light on its architecture, functionalities, optimizations, and best practices.

Understanding Oracle JVM Architecture


At its core, the Oracle JVM operates as a crucial component of the Java Runtime Environment (JRE), responsible for executing Java bytecode. Its architecture comprises various layers, each playing a pivotal role in the execution process. From the class loader subsystem to the runtime data areas and execution engine, Oracle JVM orchestrates a symphony of operations to ensure seamless execution of Java applications across diverse environments.

Delving into Oracle JVM Functionalities


Oracle JVM boasts a myriad of functionalities, each designed to enhance the performance, scalability, and reliability of Java applications. From Just-In-Time (JIT) compilation to garbage collection mechanisms, Oracle JVM leverages advanced techniques to optimize runtime performance. Furthermore, it offers extensive support for multithreading, exception handling, and dynamic class loading, empowering developers to craft robust and resilient Java applications.

Optimizing Performance with Oracle JVM


In the quest for optimal performance, developers often seek ways to fine-tune their Java applications on Oracle JVM. Leveraging various optimization techniques, such as bytecode instrumentation, compiler directives, and runtime parameters, developers can unlock the full potential of Oracle JVM. Additionally, profiling tools and monitoring utilities provide invaluable insights into application behavior, enabling developers to identify bottlenecks and optimize critical code paths for enhanced performance.

Best Practices for Oracle JVM Development


To harness the full capabilities of Oracle JVM, developers must adhere to a set of best practices that promote efficiency, reliability, and maintainability. Adopting modular design principles, optimizing resource utilization, and embracing coding standards are fundamental tenets of Oracle JVM development. Moreover, continuous integration and testing practices ensure the robustness of Java applications, mitigating risks and vulnerabilities in production environments.

Exploring Advanced Features of Oracle JVM


Beyond its core functionalities, Oracle JVM offers a plethora of advanced features and capabilities that cater to the evolving needs of Java developers. From dynamic bytecode generation to runtime instrumentation and native code integration, Oracle JVM empowers developers to push the boundaries of Java programming. Moreover, its extensible architecture enables seamless integration with third-party libraries and frameworks, further expanding the horizons of Java development.

Conclusion

In conclusion, the Oracle JVM stands as a cornerstone of Java development, empowering developers to unleash the full potential of their Java applications. With its robust architecture, advanced functionalities, and optimization capabilities, Oracle JVM continues to shape the landscape of Java programming. By understanding its intricacies and embracing best practices, developers can elevate their Java development endeavors to new heights of success.

Wednesday, November 1, 2023

The Ultimate Guide to JVM Developers: Mastering Java Virtual Machine

JVM Developers: Mastering Java Virtual Machine

Are you a tech enthusiast, a software developer, or a curious learner looking to dive into the world of Java Virtual Machine (JVM)? If you are, you've come to the right place. We, as SEO experts and high-end copywriters, are here to provide you with the ultimate resource on JVM developers, equipping you with all the knowledge you need to excel in the JVM ecosystem.

Understanding the JVM


The Java Virtual Machine (JVM) is a fundamental component of Java technology, responsible for executing Java applications. It's a virtualized platform that allows Java programs to run on a variety of hardware without modification. Let's dive deeper into what JVM is all about:

What is JVM?

JVM is a virtual machine that enables Java applications to run on various platforms without modification. It converts Java bytecode into machine code, making it a cross-platform solution.

How Does JVM Work?

JVM executes Java bytecode by Just-In-Time (JIT) compilation. It translates bytecode into machine code for the host CPU, ensuring optimal performance.

JVM Components

JVM comprises several key components, including the Class Loader, Execution Engine, and Java Native Interface (JNI). Each plays a vital role in the execution of Java programs.

Becoming a Proficient JVM Developer


Now that you have a grasp of what JVM is, let's explore how you can become a proficient JVM developer and master this versatile technology:

1. Learn Java Inside Out

To excel in JVM development, a strong foundation in Java is essential. Understand the language's syntax, features, and best practices.

2. Master the JVM Ecosystem

Familiarize yourself with the Java ecosystem, including tools like Maven, Gradle, and popular IDEs like IntelliJ IDEA and Eclipse.

3. Understanding JVM Internals

Delve deep into the inner workings of the JVM, learning about class loading, bytecode, garbage collection, and memory management.

4. Troubleshooting and Optimization

Learn to diagnose and optimize your Java applications. Proficiency in tools like JVisualVM and YourKit can make a significant difference in your development journey.

5. Java Frameworks and Libraries

Explore popular Java frameworks like Spring and libraries such as Apache Commons to enhance your Java development skills.

JVM Best Practices


To ensure your JVM-based applications are efficient and performant, consider these best practices:

1. Code Optimization

Write clean, efficient code that minimizes resource usage. Utilize profiling tools to identify bottlenecks and make improvements.

2. Garbage Collection Tuning

Understand the different garbage collection algorithms and tune them according to your application's needs for optimal memory management.

3. Thread Management

Effectively manage threads to maximize concurrency and ensure your application can handle multiple tasks concurrently.

4. Security Considerations

Be mindful of security practices when developing JVM-based applications, especially if they handle sensitive data.

5. Monitoring and Logging

Implement robust monitoring and logging to track application performance and troubleshoot issues proactively.

Advanced Topics in JVM Development


For those who want to take their JVM development skills to the next level, consider these advanced topics:

1. Java Performance Tuning

Learn the intricacies of JVM performance tuning, including JVM flags, heap management, and thread optimizations.

2. Cloud-Native JVM

Explore how JVM fits into the world of cloud-native development, containerization, and microservices.

3. JVM Alternatives

Discover alternative JVM implementations like GraalVM and OpenJ9, and evaluate when to use them.

4. Real-time Java

Explore real-time Java development and its applications in industries like finance and gaming.

Conclusion

Becoming a proficient JVM developer is a rewarding journey, opening doors to exciting career opportunities and the ability to create high-performance Java applications. With this comprehensive guide, you now have the knowledge and resources to master the Java Virtual Machine. Keep exploring, learning, and innovating, and you'll be well on your way to becoming a JVM expert.

Monday, August 28, 2023

Going inside Java 21’s rich, upcoming goodness

Final features, continuing previews, and brand-new treats—with video links


Are you ready for all the new technology in Java 21? This article will take you on a tour of many of the changes, small and large, covering final JEPs, a progressing preview, and something entirely new for the platform.

Final features


Virtual threads. Let’s start with the big one: After two rounds of preview with barely any changes, virtual threads are final in Java 21. Now web frameworks are off to the races because they need to let you easily configure using virtual threads instead of platform threads to handle requests.

Oracle Java Career, Oracle Java Skills, Oracle Java Jobs, Oracle Java Preparation, Oracle Java Tutorial and Materials, Oracle Java Certification, Oracle Java Learning, Oracle Java Guides

Configuring virtual threads has the potential to let your app handle way more concurrent connections than before. But keep in mind that virtual threads aren’t performance pixie dust, so keep expectations realistic. Then again, if you don’t see the results you’re hoping for, there may be some easy code changes you can do that get you there. Watch episode 23 of the Inside Java Newscast for more on that and some virtual thread guidelines.

Sequenced collections. Many collections in Java have a stable iteration order (all lists and some sets, for example) but don’t necessarily allow indexed access to them (all lists do, but sets usually don’t). Java 21 steps up its collections game and introduces a set of new interfaces that capture this concept and offer related functionality.

At the core of these new interfaces is SequencedCollection, which extends Collection and is ultimately implemented by all lists, some sets, and a few other data structures. It offers the addFirst, addLast, getFirst, getLast, removeFirst, and removeLast methods, which do what you’d expect.

// getting first and last elements from a list
// (sequenced by order of addition)

var letters = List.of("c", "b", "a");
"c".equals(letters.getFirst());
"a".equals(letters.getLast());

// same but from a sorted set
// (sequenced by natural ordering)

var letterSet = new TreeSet<>(letters);
"a".equals(letters.getFirst());
"c".equals(letters.getLast());

There’s also a new method called reversed that returns a SequencedCollection that is a view on the underlying collection but in reverse order, which makes it super easy to iterate or stream over the collection.

var letters = new ArrayList<>(List.of("a", "b", "c"));
var reversedLetters = letters.reversed();

letters.addLast("d");
reversedLetters.forEach(System.out::print);
// ~> dcba

reversedLetters.addFirst("e");
letters.forEach(System.out::print);
// ~> abcde

If you want to learn more about that, the companion interfaces SequencedSet and SequencedMap, and a few odds and ends, check out episode 25 of the Inside Java Newscast.

Generational low-pause garbage collection. Garbage collection is also taking big steps forward. The Z Garbage Collector (ZGC) has a strong focus on ultralow pause times, which can lead to a higher memory footprint or higher CPU usage than other garbage collectors. Starting with Java 21, both of these metrics will be improved on many workloads when ZGC becomes generational, meaning it will maintain separate generations for young objects, which tend to die young, and old objects, which tend to be around for some time.

Preliminary benchmarks show very promising results: In a probably not-representative case, Cassandra 4 showed

◉ Four times the throughput on generational ZGC compared to ZGC with a fixed heap
◉ A quarter of the heap size on generational ZGC compared to ZGC with stable throughput

If you want to give generational ZGC a try on your workload, download a Java 21 early access build and launch it with -XX:+UseZGC -XX:+ZGenerational

Pattern matching. To effectively use pattern matching, you need three things.

◉ A capable switch that allows the application of patterns
◉ The ability to enforce limited inheritance so the switch can check exhaustiveness
◉ An easy way to aggregate and deconstruct data

var shape = loadShape();
var area = switch(shape) {
    case Circle(var r) -> r * r * Math.PI;
    case Square(var l) -> l * l;
    // no default needed
}

sealed interface Shape permits Circle, Square { }
record Circle(double radius) { }
record Square(double length) { }

There are other features that come in really handy (and they are being worked on and one even previews in Java 21—more on that later), but these are the basics, and Java 21 finalizes the last two pieces: pattern matching for switch and record patterns. With these features, you can use this powerful idiom in your projects—be it in a small or large way—if you use a functional or data-oriented approach. To see how these features play together to achieve that, check out episode 29 of the Inside Java Newscast.

Key encapsulation mechanism API. Do you know the Diffie-Hellman key exchange encapsulation (DHKEM) algorithm? If you don’t, you should definitely look into it. On the face of it, the algorithm sounds impossible. It lets two parties compute an encryption key, which is a number, while preventing an observer, who sees every exchanged message, from feasibly redoing the computation, ensuring that the key is a secret that only the two parties know. As you can imagine, that’s very helpful when you need to exchange encrypted information between parties that have no prior knowledge of each other. Hence, the DHKEM algorithm is widely used, for example, to provide forward secrecy in TLS.

Like all key encapsulation mechanisms, DHKEM is a building block of hybrid public key encryption (HPKE) and will be an important tool for defending against quantum attacks. Starting with Java 21, Java has an API to represent key encapsulation mechanisms in a natural way.

Now you’re probably wondering what the API looks like. It’s all described in episode 54 of the Inside Java Newscast with Ana-Maria Mihalceanu.

New view command for JDK Flight Recorder. The JDK Flight Recorder is an amazing piece of tech, and it’s getting better with every JDK release. JDK 21 added the view command, which displays aggregated event data on the terminal. This way, you can view information about an application without the need to dump a recording file or open up JDK Mission Control. Billy Korando explains all in episode 53 of the Inside Java Newscast.

API improvements

Java 21 comes with a number of small additions to existing APIs. Let’s quickly go over them, so you’re aware of where the JDK can do your work for you.

Emoji. The Character class gained a few static checks that let you identify emojis; first and foremost is isEmoji.

var codePoint = Character.codePointAt("😃", 0);
var isEmoji = Character.isEmoji(codePoint);
// prints "😃 is an emoji: true"
System.out.println("😃 is an emoji: " + isEmoji);

Math. The Math class got static clamp methods that take a value, a minimum, and a maximum and return a value that is forced into the [min, max] interval. There are four overloads for the four numerical primitives.

double number = 83.32;
double clamped = Math.clamp(number, 0.0, 42.0);
// prints "42.0"
System.out.println(clamped);

Repeat methods. StringBuilder and StringBuffer gained repeat methods, which allow you to add a character sequence or a code point multiple times to a string that is being built.

var builder = new StringBuilder();
builder.append("Hello");
builder.append(", ");
builder.repeat("World", 3);
builder.append("!");
// prints "Hello, WorldWorldWorld!"
System.out.println(builder);

String. String’s indexOf methods gain overloads that take a maxIndex, as follows:

var hello = "Hello, World";
var earlyCommaIndex = hello.indexOf(",", 0, 3);
// prints "-1"
System.out.println(earlyCommaIndex);

Also, string’s new splitWithDelimiters method behaves like the split method but includes the delimiters in the returned array. The same splitWithDelimiters method was added to Pattern, by the way.

var hello = "Hello; World";
var semiColonSplit = hello.splitWithDelimiters(";", 0);
//prints [Hello, ;,  World]
System.out.println(Arrays.toString(semiColonSplit));

List shuffles. Need to shuffle a List in place with a RandomGenerator? Then that’s your reason to update to Java 21! Once you do, you can pass the list and a RandomGenerator to Collections::shuffle, and it’ll shuffle the list.

var words = new ArrayList<>(List.of("Hello", "new", "Collections", "shuffle", "method"));
var randomizer = RandomGenerator.getDefault();
// using this API makes way more sense when you’re not using the default generator
Collections.shuffle(words, randomizer);
// prints the words above but with a 99.17% chance of a different order
System.out.println(words);

HttpClient. An HttpClient can now be instructed to close, to shut down, or to await termination, but those are best-effort implementations that can have adverse interactions with open request or response body streams.

var httpClient = HttpClient.newHttpClient();
// use the client
httpClient.close();

// or call shutdown and awaitTermination
// yourself for more control:
var httpClient = HttpClient.newHttpClient();
// use the client
httpClient.shutdown();
httpClient.awaitTermination(Duration.ofMinutes(1));

// it also implements AutoCloseable
try (var httpClient = HttpClient.newHttpClient()) {
    // use the client
}

Locales. The Locale.availableLocales() method returns a stream of all available locales.

var locales = Locale
    .availableLocales()
    .map(Locale::toString)
    .filter(locale -> !locale.isBlank())
    .sorted()
    .collect(Collectors.joining(", "));
// prints af, af_NA, af_ZA, af_ZA_#Latn, agq, ...
System.out.println(locales);

Case-folded tags. And because you’ve all asked for case-folded IETF BCP 47 language tags (don’t pretend that you didn’t), Locale gained the caseFoldLanguageTag method.

var lang = Locale.caseFoldLanguageTag("fi-fi");
// prints "fi-FI" (note the RFC5646-correct case)
System.out.println(lang);

Continued evolution

Now it’s time to transition from finalized features to previews, incubators, and experiments. To use a preview feature, you need to add the command-line flag --enable-preview to javac and java, and you also need to specify the Java version for javac, preferably with --release 21.

Structured concurrency. Once you get abundant virtual threads and start creating one virtual thread for every little concurrent task you have, an interesting opportunity arises: You can treat threads that you created for a set of tasks as if they are executing a single unit of work, and you can see them as children of the thread that created them.

An API that capitalizes on that would streamline error handling and cancellation, improve reliability, and enhance observability. And it would make it easy and helpful to start and end that single unit of work in the same scope, defining a unique entry and exit point for handling concurrent code. It would do for concurrency what structured programming did for control flow: add much-needed structure.

Lucky us, because such an API exists! It’s called the Structured Concurrency API.

// create task scope with desired
// error handling strategy
// (custom strategies are possible)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {

    // fork subtasks
    Subtask<String> user = scope.fork(() -> findUser());
    Subtask<Integer> order = scope.fork(() -> fetchOrder());

    scope
        // wait for both subtasks
        .join()
        // propagate potential errors
        .throwIfFailed();

    // both subtasks have succeeded
    // ~> compose their results
    // (these calls are nonblocking)
    return new Response(user.get(), order.get());
} // task scope gets shut down

The Structured Concurrency API was incubating in Java 20 and is upgraded to a preview in Java 21. Beyond moving to a proper package, namely java.util.concurrent, the only change has been that StructuredTaskScope’s fork method now returns the new type Subtask. In Java 20, the fork method returned a Future, but that offered degrees of freedom (such as calling the blocking getmethod) that are counterproductive in structured concurrency and was overall too evocative of asynchronous programming, which is exactly what structured concurrency isn’t.

José Paumard has a video tutorial on all this; to see it, check out episode 13 of JEP Café.

Scoped values. The ThreadLocal API is used to store thread-specific information, usually in static final fields, which can then be queried from anywhere those variables are visible. That’s useful, for example, when a container, such as a web server, needs to make information accessible to other parts of its code that it doesn’t call directly. It’s also useful when it doesn’t want to pass that information on explicitly, either for convenience or integrity reasons.

class Server {

    // Principal needs to be visible to other code...
    final static ThreadLocal<Principal> PRINCIPAL = new ThreadLocal<>();

    void serve(Request request, Response response) {
        var level = request.isAuthorized() ? ADMIN : GUEST;
        var principal = new Principal(level);
        PRINCIPAL.set(principal);
        // ... but not the application
        Application.handle(request, response);
    }

}

However, ThreadLocal has a few shortcomings:

◉ Anyone with access to the ThreadLocal field can read its value and also set a new one.

◉ Values stored in ThreadLocal can be inherited from one thread to another. To prevent the other threads from reading an updated value (which the API should explicitly prevent; it’s thread local, after all), the inheriting thread must create copies. These drive up memory use, especially when there are many threads—you know, the whole “millions of virtual threads” thing.

◉ Once set, values must be explicitly removed (using the ThreadLocal::remove method) or they will leak beyond their intended use and continue to occupy memory.

To solve these problems, Java 20 incubated and Java 21 previews the Scoped Values API, which works by binding a value to the ScopedValue instance and passing the code that is allowed to read that value as a lambda—that’s the scope.

class Server {

    final static ScopedValue<Principal> PRINCIPAL = new ScopedValue<>();

    void serve(Request request, Response response) {
        var level = request.isAdmin() ? ADMIN : GUEST;
        var principal = new Principal(level);
        ScopedValue
            // binds principal to PRINCIPAL, but...
            .where(PRINCIPAL, principal)
            // ... only in the scope that is defined by this lambda
            .run(() -> Application.handle(request, response));
    }

}

The Scoped Values API addresses the following ThreadLocal issues:

◉ Within the scope, the bound value is immutable.
◉ Accordingly, no copies need to be created when inheriting, which significantly improves scalability.
◉ As the name implies, a scoped value is visible only within the defined scope; after that, the value is automatically removed, so it cannot accidentally leak.

To see scoped values in practice, watch episode 16 of JEP Café. In it, José Paumard talks about the early version of the API as it was in Java 20 and about what changes in Java 21; besides moving to java.lang, the changes mean the scope (the lambda) can now be a Callable, Runnable, and Supplier.

Vector API. The Vector API is in its sixth incubation and still waiting for Project Valhalla. There’s nothing new to see here, so please move on unless you want to see vectors in action. In that case, check out José Paumard’s episode 18 of JEP Café.

Foreign Function and Memory API. By efficiently invoking code outside the JVM (foreign functions) and by safely accessing memory not managed by the JVM (foreign memory), the Foreign Function and Memory API enables Java programs to call native libraries and process native data without the brittleness and danger of the Java Native Interface (JNI).

One of the main drivers of this API is to provide safe and timely deallocation in a programming language whose main staple is automatic deallocation (thanks, garbage collector).

Finding the right primitive to express this capability in a way that is harmonious with the rest of the Java programming model triggered a round of API changes in Java 20 and again in Java 21, which is why the API will take another round of previewing.

// 1. find foreign function on the C library path
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle radixsort = linker.downcallHandle(stdlib.find("radixsort"), ...);

// 2. allocate on-heap memory to store four strings
String[] words = { "mouse", "cat", "dog", "car" };

// 3. use try-with-resources to manage the lifetime of off-heap memory
try (Arena offHeap = Arena.ofConfined()) {
    // 4. allocate a region of off-heap memory to store four pointers
    MemorySegment pointers = offHeap
        .allocateArray(ValueLayout.ADDRESS, words.length);
    // 5. copy the strings from on-heap to off-heap
    for (int i = 0; i < words.length; i++) {
        MemorySegment cString = offHeap.allocateUtf8String(words[i]);
        pointers.setAtIndex(ValueLayout.ADDRESS, i, cString);
    }

    // 6. sort the off-heap data by calling the foreign function
    radixsort.invoke(pointers, words.length, MemorySegment.NULL, '\0');

    // 7. copy the (reordered) strings from off-heap to on-heap
    for (int i = 0; i < words.length; i++) {
        MemorySegment cString = pointers.getAtIndex(ValueLayout.ADDRESS, i);
        words[i] = cString.getUtf8String(0);
    }

// 8. all off-heap memory is deallocated at the end of the try-with-resources block
}

On that note, the quality of feedback during the preview phase from projects adopting the API has been excellent and very important for its evolution. If you want to help move Java forward, the easiest way to do that is to experiment with preview features and report back to the respective mailing lists.

Another addition in Java 21 has been the so-called fallback linker, which offers a way for platforms to be compliant with Java SE without too much work by using libffi instead of fully implementing the Linker API.

Goodbye 32-bit Windows port. Microsoft Windows 10 was the last 32-bit version of Windows, and it reaches the end of its lifecycle in October 2025. As no surprise, the Java port for 32-bit Windows isn’t heavily maintained anymore. For example, its implementation of virtual threads isn’t virtual at all—the threads fall back to platform threads. So, I guess it was to be expected that the port got deprecated for removal, which Java 21 does.

Brand-new previews

In Java 21 there are three brand-new preview features that I just can’t skip—and I love how diverse they are! They span from improving a Java workhorse to refining a programming paradigm to changing how beginners learn the language.

Unnamed classes and instance main methods. Java 21 allows for much simpler entry points into a Java program. The main method no longer needs to be public or static, nor does it need the args array. And the whole surrounding class becomes optional, too, making void main the smallest possible Java program.

// content of file Hello.java
void main() {
    System.out.println("Hello, World!");
}

You can watch me demonstrate this in episode 49 of the Inside Java Newscast. Let me briefly clarify two points that I didn’t explain very well in that video.

◉ This is a preview feature, so if you use it in a single source–file program, where it clearly shines, you need to add --enable-preview --source 21 to the java command as follows:

java --enable-preview --source 21 Hello.java

◉ There are plans to shorten System.out.println to just println and to also offer a more succinct way to read from the terminal, but neither of those are part of Java 21.

Unnamed variables and patterns. Unused variables are annoying but bearable. Unused patterns during deconstruction, on the other hand, are really cumbersome and clutter code because they make you want to deconstruct less.

String pageName = switch (page) {
    case ErrorPage(var url, var ex)
        -> "💥 ERROR: " + url.getHost();
    case ExternalPage(var url, var content)
        -> "💤 EXTERNAL: " + url.getHost();
    case GitHubIssuePage(var url, var content, var links, int issueNumber)
        -> "🐈 ISSUE #" + issueNumber;
    case GitHubPrPage(var url, var content, var links, int prNumber)
        -> "🐙 PR #" + prNumber;
};

Therefore, it’s really good that Java 21 turns the underscore into a special variable and pattern, which effectively says, “I won’t be used, and you can have as many of me as you want in the same scope.”

String pageName = switch (page) {
    case ErrorPage(var url, _)
        -> "💥 ERROR: " + url.getHost();
    case ExternalPage(var url, _)
        -> "💤 EXTERNAL: " + url.getHost();
    case GitHubIssuePage(_, _, _, int issueNumber)
        -> "🐈 ISSUE #" + issueNumber;
    case GitHubPrPage(_, _, _, int prNumber)
        -> "🐙 PR #" + prNumber;
};

The change makes code more clearly readable and reduces IDE and compiler warnings. Best of all, though, it makes switching over sealed types more maintainable by allowing you to easily combine default handling of different types into a single branch while avoiding an outright default branch.

String pageEmoji = switch (page) {
    case GitHubIssuePage _ -> "🐈";
    case GitHubPrPage _ -> "🐙";
    // explicitly list remaining types to avoid default
    // branch (only possible with unnamed patterns)
    case ErrorPage _, ExternalPage _ -> "n.a.";
};

If you want to better understand why that’s important and how exactly unnamed variables and patterns work, watch episode 46 of the Inside Java Newscast.

String templates. The practice of embedding variables or simple expressions into strings isn’t popular. One reason is that it’s a bit cumbersome and the code is not perfectly readable. But more importantly, if the embedded content comes from the user, there’s the risk of injection attacks. And generally, unless you’re creating text for humans to read, there’s probably syntax and escaping to consider.

// a cumbersome and dangerous example
String property = "last_name";
String value = "Doe";

String query = "SELECT * FROM Person p WHERE p."
    + property + " = '" + value + "'";

String templates solve those problems. They make it easy to embed expressions in string literals or text blocks by encasing them between an opening backslash followed by an opening curly brace and a closing curly brace. They also enforce processing of such string templates by domain-specific string processors.

Such processors receive the string portions and the variables separately and return instances of any type.

The obvious processor simply concatenates and returns a String, but there are other possibilities out there. A SQL processor could validate and parse a statement’s syntax and return a java.sql.Statement, and a JSON processor could return a JsonNode.

// a safe and readable example
String property = "last_name";
String value = "Doe";

Statement query = SQL."""
    SELECT * FROM Person p
    WHERE p.\{property} = '\{value}'
    """;

If you want to dig deeper, check out episode 47 of the Inside Java Newscast by Ana-Maria Mihalceanu.

Source: oracle.com

Monday, August 14, 2023

Inside the JVM: Arrays and how they differ from other objects

Arrays are unique objects inside the JVM, and understanding their structure makes for better coding.


The simplest way of classifying Java data items is to divide them into primitives and objects. Primitives, as most Java developers know, comprise booleans, bytes, chars, the integer variants (short, int, and long), and the floating-point variants (floats and doubles). Inside the JVM, these primitives are instantiated in a raw form. The declaration of an int creates a 32-bit signed integer field for the JVM to work with. These primitives are most often created on the operand stack that is constructed for every method invocation. (The notable exception is static primitives, which are created on the heap.)

Inside the JVM: Arrays and how they differ from other objects

In contrast to the simply allocated primitives, objects are entities that surround the data item with methods and sometimes with additional supporting fields. For example, a String object contains an array (which I’ll discuss shortly) that holds the contents of the string and supplementary fields that are used by a variety of methods defined for String. Objects are created on the heap. That is, they are allocated from free memory.

All objects—except arrays—have a constructor. If the source code does not define a constructor for a new object, a no-parameter constructor is created for it by the Java compiler. Most often, this constructor calls the default constructor in the Object class, which simply returns—that is, it does nothing.

The nature of arrays


Arrays are objects. However, inside the JVM, arrays are notably different from all other objects. The first major difference is that arrays are created by the JVM—not by an implicit or explicit call to new() by the developer. When the Java compiler first comes upon a set of brackets attached to a variable name, it emits a specific bytecode that tells the JVM to create an array. The compiler also specifies the kind of data items the array will hold (either primitives or objects) and how many dimensions the array has.

The JVM next creates an array of the appropriate size and type and wraps it up as an object. That is, all the methods available in Object—which arrays inherit—for example, toString(), are available to arrays. The elements of the last dimension of a newly created array are initialized to the default value for the data type (zero for the numeric types, null for objects).

Initializing arrays


As mentioned previously, arrays lack constructors. No default constructor is created by the Java compiler, and no constructor can be specified by the developer. One implication of this is that arrays must be initialized explicitly to their desired values. This is typically done through a for loop or directly at the time of the array declaration, as follows:

importantYears = new int[] {800, 1066, 1492,};

(Note that the comma after the last value is accepted in Java and won’t cause an error.) Java does not allow initialization of selected elements using the previous syntax. You must initialize a specific element individually.

Another curiosity of Java arrays is that they can have a size of zero.

unimportantYears = new int[0];

This code will not result in an error message. This surprising feature is used primarily by code generators, which might create an array and then discover there are no values to place in it. In this example, unimportantYears is not null; instead, it’s an empty array. In the same manner, a zero-length string is not null, but rather it’s a viable object.

Multidimensional arrays


While single-dimension arrays have their quirks, multidimensional arrays contain a lot more curious magic. Here is an example of a three-dimensional array, representing the x, y, and z dimensions of the three values for a financial transaction.

points = new int[2][3][4]; // a point or 1% in interest

When the compiler encounters this code, it emits a unique bytecode, MULTIANEWARRAY, which creates an array with dimensions that are each set to the specified size. This array is implemented as an array of arrays. That is, the first two dimensions contain only pointers to other arrays. So, for example, when you access the data item at 1, 2, 0, the 1 points not to a series of values but to an array of pointers to arrays of pointer values. Those values each point to yet another array—the array of integers. Put another way, points is an array of two pointers to arrays of three pointers to arrays of four ints. Figure 1 shows this design pictorially.

Inside the JVM: Arrays and how they differ from other objects
Figure 1. A three-dimensional array as it’s created inside the JVM

If you think of this design as a tree, you’ll note that only leaf arrays contain actual values. This is somewhat counterintuitive. Two-dimensional arrays are often thought of as tables. (Strictly speaking, there is no tabular analogy in the JVM’s representation of a two-dimensional array; it’s not a rows-and-columns construct.)

This design has important performance implications. The first is that to access an individual element in this example array, the JVM must dereference three pointers to get to the integer. For accessing individual values intermittently, that process represents little overhead. However, for multidimensional arrays where you are frequently updating all the values at once, such as via a for loop, the numerous dereferencing of pointers incurs significant overhead.

One way to reduce this overhead is to consider unfolding the arrays into a single-dimension array. For example, make it a 1 x 24 array and then map the three coordinates yourself to the intended element in the array. Then, updating all the values in the array can be done quickly with greatly reduced overhead. As with all things, performance should be measured carefully to make sure the trade-off is worthwhile.

Array size and the concept of arrays of arrays


Many Java collections have a method called size(), which returns an integer stating the number of elements in the collection. Arrays have no such method. There are several reasons for this, but the principal one is that arrays are simple Object instances—they are not collections. The Object class has no size() method, so arrays don’t either.

Arrays, however, have a property called length, which can be queried to get the number of elements in the specified array. In a single-dimension array, such as the first example in this article, the following code would be equal to 3.

importantYears.length

With multidimensional arrays, the same query gives a perhaps unexpected result. Using the previous points array, points.length is equal to 2, rather than the value of 24 that you might expect. The reason is that points is considered only as an array of two elements (which happen to be pointers to other arrays). If you want to get the size of all the dimensions, you need to write the following:

System.out.printf("\n Length of points: %d", points.length);
System.out.printf("\n Length of points[0]: %d", points[0].length);
System.out.printf("\n Length of points[0][0]: %d", points[0][0].length);

The code above prints the following:

Length of points: 2
Length of points[0]: 3
Length of points[0][0]: 4

As you can see, what you have is truly three arrays, working together to create the equivalent of a three-dimensional array. So, to get the size of each dimension, you need to specify exactly which dimension you want. (It’s somewhat counterintuitive that the zero dimension is not the first one in the array.)

Here’s an interesting question: What would happen in a multidimensional array if one of the dimensions were declared with a size of 0? For example,

strangePoints = new int[3][4][0][2]

In this declaration, all dimensions after the zero-size dimension are ignored. So, the result of this declaration is equivalent to a two-dimensional array of ints. This makes sense because a zero-size dimension would contain no pointers, so it’d be unable to point to subsequent layers.

Back inside the JVM


Eagle-eyed readers of my earlier statement about length being a field rather than a method call might wonder how a direct subclass of Object would have a field called length to begin with, as Object has no such field. The answer is that there is a little magic going on inside the Java compiler. When the compiler detects a reference to the length of an array, it emits a special bytecode, ARRAYLENGTH, which obtains the length of the array and returns it. This looks and behaves like a method call, but all method calls in the JVM require one of a small set of bytecodes, and they are implemented via the creation of a new frame with stack allocation and several other operations. None of that happens with this special bytecode.

No other Java objects have a corresponding bytecode for determining their size. This is just one of the many aspects that make arrays entirely unique entities inside the JVM. So, now when you code arrays, you’ll know there’s magic going on, and you’ll understand how to use the magic to get the behavior you’re looking for.

Source: oracle.com

Monday, June 26, 2023

JVM

The Untold Story of JVM Internals: Unveiling the Inner Workings of Java's Engine

JVM Internals, Oracle Java Career, Oracle Java Skills, Oracle Java Jobs, Oracle Java Prep, Oracle Java Preparation, Oracle Java Tutorial and Mateirals

Introduction


Welcome to an in-depth exploration of the Java Virtual Machine (JVM), the powerhouse behind Java's exceptional performance and platform independence. In this comprehensive article, we will delve into the intricacies of JVM internals, uncovering its hidden mechanisms and shedding light on its remarkable functionality. Prepare to embark on a journey that reveals the untold story of JVM internals, exposing the inner workings of Java's engine.

Understanding the JVM


The Java Virtual Machine is an integral component of the Java platform, responsible for executing Java bytecode and ensuring cross-platform compatibility. By interpreting the bytecode into machine-specific instructions, the JVM enables Java programs to run seamlessly on various operating systems.

Class Loading and Verifying


At the heart of JVM internals lies the class loading and verifying process. When a Java program is executed, the JVM dynamically loads classes as they are needed, ensuring efficient memory management. This dynamic loading mechanism allows for flexibility and adaptability in Java applications, enabling them to evolve dynamically.

Additionally, the JVM verifies the loaded classes to ensure their integrity and security. Through a series of checks, the verifier examines the bytecode for potential errors or security vulnerabilities, mitigating the risks associated with malicious code execution.

Memory Management and Garbage Collection


Efficient memory management is a crucial aspect of JVM internals. Java's garbage collection mechanism automatically reclaims memory occupied by objects that are no longer in use, preventing memory leaks and optimizing resource utilization. The JVM employs various garbage collection algorithms, such as the generational garbage collector, to efficiently manage memory and enhance performance.

Just-In-Time Compilation (JIT)


One of the JVM's most remarkable features is its ability to dynamically optimize code execution through Just-In-Time (JIT) compilation. The JVM analyzes frequently executed bytecode and compiles it into native machine code, significantly improving the performance of Java applications. This adaptive compilation process ensures that critical code paths are executed efficiently, providing enhanced speed and responsiveness.

Runtime Data Areas


To facilitate the execution of Java programs, the JVM utilizes runtime data areas, which include the method area, heap, stack, and program counter. These areas store critical information such as class metadata, object instances, method invocations, and thread-specific data. By meticulously managing these runtime data areas, the JVM ensures efficient memory allocation and execution of Java applications.

Advanced Optimizations and Performance Tuning


In addition to its fundamental mechanisms, the JVM offers advanced optimizations and performance tuning options to further enhance the execution of Java programs. By fine-tuning various JVM parameters, developers can optimize memory allocation, garbage collection behavior, and thread management, among other aspects. These optimizations play a pivotal role in maximizing the performance and scalability of Java applications, empowering developers to create robust and efficient software solutions.

Conclusion

In conclusion, the JVM's internals constitute a fascinating realm of technology, where intricate mechanisms combine to power the Java platform. Through its class loading, memory management, JIT compilation, and runtime data areas, the JVM ensures optimal performance, security, and cross-platform compatibility. By gaining a deeper understanding of JVM internals, developers can unlock new possibilities and unleash the true potential of their Java applications.

Monday, May 15, 2023

JVM

Reduce technical debt by valuing comments as much as code


Comments have long occupied a distinctly secondary place in the output of developers. Comments are so secondary, and so undervalued, that in today’s code they appear infrequently except as routine copyright and licensing headers at the top of files. This is a lost opportunity because good comments that are actively maintained are one of the most effective ways of reducing technical debt—that is, they lighten the workload of future programmers who will work on the code.

Because the culture of many development organizations undervalues comments, team leads (and managers) allow comments to get out of sync with the code—thereby increasing technical debt instead of reducing it.

And because undervaluing is the rule, developers learn to blame comments rather than the culture that allows them to get out of sync with the code. This negative view is articulated by Bob Martin’s assertion that every comment represents “a failure to express yourself in code” (see Figure 1). Note that in his book Clean Code, Martin makes this curious statement and then spends 15 pages enumerating the many instances when comments are highly useful.

Oracle Java, Java Career, Java Prep, Java Learning, Java Tutorial and Materials, Java Certification, Java Guides
Figure 1. Bob Martin has strong views about comments.

Why write comments?


Comments eliminate technical debt in several ways.

Comments explain the code’s purpose. A comment at the beginning of a file explaining what functionality is found in the code removes the need to read through all methods to gather an understanding of the file. If you’re looking for a specific file, being able to read a single sentence that describes its contents greatly speeds the search. For this reason, most coding standards urge the inclusion of just such a comment in each file.

The use of keywords in file-level comments can further facilitate searching through a large codebase for specific functionality.

Javadoc comments at the beginning of classes can substitute for file-level comments. If the comments describe the class’s role in the larger project, they further aid the reader. A description of why the class is needed, if it’s not obvious, helps others understand the class’s role. And these comments are an especially useful place for presenting any unusual information in the API of the class.

Comments explain the code. Many developers have the quixotic belief that if the code is clear enough, they don’t need to use comments. This is what Bob Martin is asserting in Figure 1. It is a lovely premise that does not hold—at all. The first problem is that most developers are under great time pressure and don’t have the time to make the code so utterly clear that it requires no further comment. In fact, the far more common experience programmers have is looking at code they wrote just six months earlier and thinking “I can’t believe I wrote that!” Don’t kid yourself that you can write code so clear that it requires no comments to be understood.

Another limitation of the clear-code objection is that code explains only how a thing is done, not why it is done that way, especially if there are obvious alternatives; if the why is not evident and obvious, technical debt accrues in the absence of an explanation. Note that without such explanatory comments, code can be exceedingly difficult to maintain because no one dares to touch it, which is the very definition of technical debt.

Comments highlight the potential gotchas. Google’s Java guidelines, for example, require a comment anytime a case statement in a switch falls through to the next case. Likewise, the guidelines require noting and explaining an empty exception catch block, as well as all other items that are either potentially overlooked, not conventional, or a frowned-upon but occasionally necessary step.

Comments provide landmarks for future work. The term self-admitted technical debt refers to the TODO and FIXME comments often encountered in codebases. On the surface, these look like detritus left behind by well-meaning programmers who were subsequently occupied with other tasks. However, an interesting study on well-maintained open source projects, “An empirical study on the removal of self-admitted technical debt,” found that 74% of such entries are removed within 180 days, most often by the developers who put them there. In other words, such comments often represent useful landmarks for future work, rather than detritus.

While not specifically a countermeasure to technical debt, I use a third marker, CURR, for “current.” This is a breadcrumb of sorts that tells me where I left off coding. I often include several lines of comments containing the information I might need when resuming work. I allow only one CURR in a codebase, so if I resume work elsewhere, I convert the existing CURR to a TODO.

Oracle Java, Java Career, Java Prep, Java Learning, Java Tutorial and Materials, Java Certification, Java Guides
Figure 2. An example of excellent commenting.

The Ousterhout approach to comments


The benefits of comments in the previous scenarios cannot be denied. They reduce technical debt, and well-maintained comments also make it easier for new team members to understand the codebase without spending hours spelunking through dead ends or posing hundreds of questions to existing staff.

However, a culture of valuing comments can encourage their use for far more than remediation of technical debt.

John Ousterhout’s excellent book, “A Philosophy of Software Design,” describes best practices for large codebases; it picks up where Martin leaves off and is much more oriented towards real-world situations. Ousterhout was the designer and implementer of the Tcl language; he later cofounded Electric Cloud, a company that specialized in DevOps tools for massive codebases (that is, codebases with more than 1 million lines of code).

While Ousterhout’s book is only 175 pages, it dedicates two full chapters to comments. The first chapter replies to the most common objections to writing and valuing comments; the second focuses on refining the quality of comments.

Ousterhout’s general dictum is that comments in code should convey whatever is in the programmer’s mind that cannot be reflected accurately in the code itself. This is an excellent guideline and one that directly countermands the contention that “every comment is a failure.” The real failure is omitting clarifying and contextual information a subsequent coder might need.

Comments can also help you clarify your own thinking. More than once, as I was writing comments for a piece of code, I recognized a defect in my implementation. This is much the same sensation as describing a problem to a colleague and having the solution appear as you’re explaining the problem—a common experience. I described an extension of this benefit in a hybrid approach that takes elements from Ousterhout’s book.

When you create a class, consider using the following useful steps, which are a significant expansion of the use of comments:

◉ Write the class interface comment first.
◉ Write the interface comments and signatures of the most important public methods, but leave the method bodies empty.
◉ Write comments and declarations for the most important instance variables.
◉ Fill in the bodies of the methods, adding implementation comments as you go along.
◉ As you discover the need for more methods, write the comments before the body.

According to Ousterhout’s experience, the benefits of these steps are threefold.

◉ When the code is done, it’s properly commented and the comments are entirely up to date.
◉ The comment-first approach enables you to focus on the abstractions rather than being distracted by the implementation.
◉ The comments reveal code complexity—if a method or variable requires a long, complex comment, it probably needs to be rethought and simplified.

That’s a lot of benefits!

Of the things to comment, the most important in Ousterhout’s view are abstractions (which are difficult to tease out from reading the implementation code) and an explanation of why the code exists. In sum, a developer working on your code for the first time should be able to scan the class’s comments and have a good idea of what the class does and understand the most important implementation aspects.

If this approach appeals to you—as it does to me—Ousterhout suggests that you use it until you’re accustomed to writing code this way. He contends, and I agree, that doing so will convert you by delivering cleaner, clearer code that is fully commented.

The luxury of Javadoc


Java developers have an excellent tool for pursuing this deeper approach: Javadoc annotations. If you write code in more than one language, you’ll quickly come to realize how paltry the commenting options are in other languages. Most languages do not have any built-in counterpart to Javadoc. Or, as in the case of Go and a few other languages, if they have a counterpart, it’s threadbare by comparison.

Javadoc lets you convert comments into elegant, well-formatted, and usable documentation. And as you can see from the excellent docs in the JDK distribution, the Java team has made full use of the tool in a thoughtful, conscientious discipline. (By the way, the JVM itself is extensively commented.)

Comments in open source projects


A final note: If you’re contributing to an open source project, you should deeply value comments. One of the biggest obstacles prospective collaborators have when considering joining a project is the difficulty of learning the codebase—most often because it contains no comments (outside of the license header).

Good open source project leaders work hard at providing accessible documentation and well-commented code so that potential contributors can grok the code easily and quickly fix a small bug without excessive effort. Projects that pride themselves on good documentation and heavy commenting, such as Redis, are rewarded with good participation.

In one project I work on, Jacobin, which is a JVM written in Go, the contributors use comments extensively. Potential contributors are also provided with an additional resource: an overview of the codebase that makes it easy to understand the overall architecture of the project and how it maps to the code layout.

These steps take time, but they help the project’s contributors as much as they help the project’s intended audience by forcing Jacobin’s developers to be explicit in our assumptions, clear in our thinking, and disciplined in our work.

Conclusion


Poorly maintained comments are a clear source of technical debt. To avoid this problem, value comments as much as you do code. For example, when doing code reviews, pay particular attention not only to the comments around the new code but also to whether higher-level comments in the file are still correct in light of the new functionality.

Because the JDK bundles Javadoc, you have the unique opportunity to thoroughly benefit from disciplined appreciation of comments—thereby eliminating technical debt.

Source: oracle.com

Monday, May 8, 2023

The performance implications of Java reflection

Reflection slows down your Java code. Why is that?


Reflection is powerful—and often misunderstood. This article will build on the introduction of the Core Reflection API introduced in “Reflection for the modern Java programmer” and discuss two major additional topics: how reflection is implemented in the HotSpot JVM and the changes made to reflection in recent versions of the Java platform.

Oracle Java Career, Java Skills, Java Jobs, Java Tutorial and Materials, Java Guides, Java Learning

This discussion begins by exploring a simplified form of the reflection mechanism’s code from the JDK. The code in the following examples resembles the implementation in Java 8, but some complexity has been removed for clarity. This basic version is used in all but the most recent Java versions. (A future article will look at the latest changes.)

Start by looking at the invoke() method on Method, which looks like the following:

public Object invoke(Object obj, Object… args)
    throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException {

  if (!override) {
    if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
      Class<?> caller = Reflection.getCallerClass();
      checkAccess(caller, clazz, obj, modifiers);
    }
  }
  MethodAccessor ma = methodAccessor;
  if (ma == null) {
    ma = acquireMethodAccessor();
  }
  return ma.invoke(obj, args);
}

The code first checks to see if the override flag has been set; it will have been set if setAccessible() has been called. Next, a reference to a MethodAccessor object is obtained, and then the invoke() call is delegated to it.

(Note: Many of the classes in this section are not in an API package of java.base, so they cannot be called directly in modern Java code. For example, MethodAccessor is in jdk.internal.reflect.)

The MethodAccessor interface is the key to the reflective invocation capability. It acts as a straightforward delegate.

public interface MethodAccessor {
    /** Matches specification in {@link java.lang.reflect.Method} */
    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException;
}

The first time this code is called, the method acquireMethodAccessor() creates an instance of the type DelegatingMethodAccessorImpl that implements MethodAccessor, as follows:

import java.lang.reflect.InvocationTargetException;

/** Delegates its invocation to another MethodAccessorImpl and can
    change its delegate at runtime. */

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
  private MethodAccessorImpl delegate;

  DelegatingMethodAccessorImpl(MethodAccessorImpl delegate) {
    setDelegate(delegate);
  }

  public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException {
    return delegate.invoke(obj, args);
  }

  void setDelegate(MethodAccessorImpl delegate) {
    this.delegate = delegate;
  }
}

As the comment explains, the purpose of this class is to act as an appropriate amount of indirection and provide a delegation point that can be updated at runtime. The initial delegate is an instance of the class NativeMethodAccessorImpl.

class NativeMethodAccessorImpl extends MethodAccessorImpl {
  private Method method;
  private DelegatingMethodAccessorImpl parent;
  private int numInvocations;

  // ...

  public Object invoke(Object obj, Object[] args)
          throws IllegalArgumentException, InvocationTargetException {

    if (++numInvocations >
          ReflectionFactory.inflationThreshold()) {
      MethodAccessorImpl acc = (MethodAccessorImpl)
          new MethodAccessorGenerator()
            .generateMethod(method.getDeclaringClass(),
                            method.getName(),
                            method.getParameterTypes(),
                            method.getReturnType(),
                            method.getExceptionTypes(),
                            method.getModifiers());
        parent.setDelegate(acc);
    }

    return invoke0(method, obj, args);
  }

  private static native Object invoke0(Method m, Object obj, Object[] args);

  // ...
}

This code contains an if block that will be entered after an invocation threshold is reached, such as after the reflective method has been called a certain number of times. If the invocation threshold has not yet been reached, the code proceeds with the native call.

Once the threshold has been reached, NativeMethodAccessorImpl will use a code generation factory, contained in MethodAccessorGenerator.generateMethod(), to create a custom class that contains bytecode that calls the target of the reflective call.

After creating an instance of this dynamically created class, the call to setDelegate() uses an uplevel reference to the parent accessor to replace the current object with acc, the newly created custom object.

For technical reasons related to class verification, the JVM must be aware of the special nature of the reflective accessor classes. For this reason, there is a special accessor class in the inheritance hierarchy that acts as a marker for the JVM. The precise details of this need not concern you, so don’t worry.

Overall, the mechanism as described represents a performance trade-off—some reflective calls are made only a few times, so the code generation process could be very expensive or wasteful. On the other hand, switching from Java into a native call is slower than remaining in pure Java. This approach allows the runtime to avoid code generation until it seems likely that the reflective call will be made relatively often.

As a result, the costs of code generation can then be amortized over the lifetime of the program, while still providing better performance for later calls than the native implementation can achieve.

Reflecting on reflection


You can see more of the reflection subsystem in action (and some recent changes) by reflecting on the subsystem itself. Assume the class has the following two methods on it—one is a simple method that just prints a message, and one is a reflective accessor for it:

public static void printStr() {
    System.out.println("Hello world");
}

public Method getMethodObj() throws NoSuchMethodException {
    var selfClazz = getClass();
    var toStr = selfClazz.getMethod("printStr");
    return toStr;
}

Additionally, there’s the following calling code (exception handling has been elided for clarity):

var m = self.getMethodObj();
Class<?> mClazz = m.getClass();
System.out.println(mClazz);
// This is necessary due to some aspects of lazy evaluation
m.invoke(null);

var f = mClazz.getDeclaredField("methodAccessor");
f.setAccessible(true);
Object ma = f.get(m);
System.out.println(ma.getClass());

The code above produces the following output when run with Java 11:

$ java javamag.reflection.ex2.ReflectTheReflect
class java.lang.reflect.Method
Hello world
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by javamag.reflection.ex2.ReflectTheReflect (file:/Users/ben/projects/writing/Oracle/Articles/reflection/src/main/java/) to field java.lang.reflect.Method.methodAccessor
WARNING: Please consider reporting this to the maintainers of javamag.reflection.ex2.ReflectTheReflect
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
class jdk.internal.reflect.DelegatingMethodAccessorImpl

There are two things to be aware of here.

◉ First, the initial reflective invocation of the method is required. If that initial invocation is omitted, the code will fail. This is due to lazy initialization of the reflection subsystem: For performance reasons, the Method objects are not populated unless they are needed.
◉ Second, if you now switch to Java 17, the code will fail with the following output:

$ java javamag.reflection.ex2.ReflectTheReflect
class java.lang.reflect.Method
Hello world
java.lang.NoSuchFieldException: methodAccessor
       at java.base/java.lang.Class.getDeclaredField(Class.java:2610)
       at javamag.reflection.ex2.ReflectTheReflect.main(ReflectTheReflect.java:15)

The failure happens because of the changes in visibility, access control, and reflection, as discussed in “A peek into Java 17: Encapsulating the Java runtime internals.” You can no longer assume that reflection allows you to poke around inside the platform without limits.

How reflection affects performance


It’s easy to imagine that the flexibility of reflection comes at a price in terms of runtime performance. The obvious, immediate question that comes to mind is this: “How big is that cost?” However, this question carries a hidden assumption—that the question is meaningful and well-formed in the first place. It’s not always that easy.

You can, of course, write a Java Microbenchmark Harness (JMH) benchmark that compares a reflective call to a direct one. Such a benchmark could look a bit like the following. The JMH annotations can be found in the package org.openjdk.jmh.annotations.

@State(Scope.Thread)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
public class SimpleReflectionBench {

    private static Method getTime = null;
    private static Object o = null;

    static {
        try {
            var clazz = ReflectionHolder.class;
            var ctor = clazz.getConstructor();
            o = ctor.newInstance();
            getTime = clazz.getMethod("getTime");
        } catch (Exception x) {
            throw new RuntimeException(x);
        }
    }

    @Benchmark
    public long runReflective() throws InvocationTargetException, IllegalAccessException {
        Object ret = getTime.invoke(o);
        return (long)ret;
    }

    @Benchmark
    public long runDirect() {
        var opt = (ReflectionHolder)o;
        return opt.getTime();
    }

    static class ReflectionHolder {
        public ReflectionHolder() {}

        public long getTime() {
            return System.currentTimeMillis();
        }
    }
}

The details will vary a bit depending on which hardware platform you are using, but a typical result will be on the order of a 23% hit to performance.

But no real Java program consists of just a single call. In reality, this code runs within an application process (the JVM), and you can’t easily isolate the performance of the executing application code from the JIT compiler, memory management, and other runtime subsystems present in that application process.

In addition, the JIT compiler performs heavy optimization and transformation of the program—and the precise details of how the program is transformed depend very much on the program.

For example, one of the most powerful transformations that the JIT compiler performs is automatic method inlining. This is, in fact, one of the first transformations to be carried out, because combining method bodies brings more code into the view of the JIT compiler. This potentially allows optimizations that would not have been possible if the JIT compiler could look only at the code of a single method at a time.

Unfortunately, reflective calls are not typically inlined in the general case, due to their dynamic nature. Note the word typically, as there are caveats that apply to this statement.

As you’ve already seen, the reflection implementation generates Java bytecode (using MethodAccessorGenerator.generateMethod()) for the call. This can make it easier for the JIT compiler to inline the reflective call if certain conditions hold, such as the Method object being rooted in a static final field and the target method being static (or having a definitely known receiver type).

Overall, this means that the actual overhead of reflective calls is not easy to reason about from first principles, but it can easily be much, much larger than 23% due to the operation of inlining on direct calls, which is not really possible for equivalent reflective calls.

The main conclusion this leads to is that you must question what a simplistic figure such as 23% means—or if it means anything at all.

To quote Brian Goetz, “The scary thing about microbenchmarks is that they always produce a number, even if that number is meaningless. They measure something; we’re just not sure what.”

The small-scale performance effects are effectively smoothed out by dealing with a larger aggregate (that is, the whole system or a subsystem), but that makes it very hard or impossible to make general, code-level recommendations for writing performant applications.

You can say that application performance is an emergent phenomenon, as it arises from the natural scale of your applications (potentially thousands of classes or millions of lines of code), and there is not necessarily any single root cause (or small set of root causes) for a specific gain or loss.

This fact is what leads performance professionals to urge developers to simply write good, clear code and let the runtime take care of optimization.

A performance tip is quite often just a workaround for some quirk of the runtime—and if application developers are aware of the quirk, it is quite likely that so are the JVM developers—and that they are working to fix it. To illustrate this, in the case of reflection, you can ask, “What JVM-level mechanisms are involved that could complicate the overall picture of the effect?”

Figure 1 shows a single line of Java code that executes a reflective call on a Method object denoted as M, with arguments x and y. It has been annotated to show the major aspects of runtime behavior that might have a performance impact on the execution of the call.

Oracle Java Career, Java Skills, Java Jobs, Java Tutorial and Materials, Java Guides, Java Learning
Figure 1. JVM effects on reflection

Here are four big areas that could affect performance.

◉ Boxing occurs at several different places.
◉ The call site for a reflective call is said to be megamorphic (many possible implementations for the method) because in the implementation up to Java 17, each instance of Method has a different, dynamically spun method accessor object.
◉ The methodAccessor field on Method is volatile, so rereading it is mandatory. Therefore, the invoke() call leads to an indirection and a virtual dispatch on the delegate.
◉ Method accessibility checks are performed on each call.

To dig a little deeper, examine the signature of the invoke() method on Method.

public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException, InvocationTargetException

This is the most general signature for a Java method that you can write—and it has to be. Reflective calls could be of any signature, and that type information will not, in general, be available until runtime. Therefore, if Method represents the capability to call a method, and invoke() represents the actual call, it is entirely logical that the signature must be this general.

Java’s type system is not single-rooted, so any primitives that appear will be handled via boxing.

Separately, the accessibility check is important, because access control is normally checked at classloading time (and any poorly behaved classes that attempt to infringe on it are not loaded). Use of reflective code changes this picture, and the Core Reflection API has a couple of weaknesses (or necessary evils, depending on how you feel about them).

◉ You can get a reflection object corresponding to a method that you would not be able to call directly.
◉ You can break the rules of the Java language by allowing calling code to selectively disable access control using setAccessible().

Therefore, the implementation of Method must check whether the Method object requires an access control check on every call (and perform it, if so). It is necessary to do this on every call because the Method object may have had setAccessible() called on it after the previous call.

These accessibility checks impact performance, as you can see by modifying the benchmark as follows:

static {
        try {
            var clazz = ReflectionHolder.class;
            var ctor = clazz.getConstructor();
            o = ctor.newInstance();
            getTime = clazz.getMethod("getTime");
            // Disable access control checking
            getTime.setAccessible(true);
        } catch (Exception x) {
            throw new RuntimeException(x);
        }
    }

Comparing the results from this benchmark to the reflective benchmark in the previous case shows that a call that is not checked for access control seems to run faster. However, the overall aggregate effect is still unknown, so you must avoid making any dubious conclusions about the general usefulness of disabling access control for reflective calls.

As stated previously, the proper object of study for performance is the whole application. This suggests that you could, for example, recompile the JDK with a code change so that access control is always ignored. This would, in theory, give a better number for at least one aspect of reflective performance in aggregate.

However, if you did do this, can you really be sure that globally disabling reflective access control checks doesn’t change semantics anywhere in the application—or in the libraries that it depends upon? After all, perhaps a framework that you’re using has a clever strategy that probes possible methods for reflective invocation and relies on the access control semantics to improve performance.

If it did, how would you know?

Source: oracle.com