Friday, May 12, 2023

The hidden and not-so-hidden gems in Java 20

Oralce Java, Java Exam, Java Exam Prep, Java Prep, Java Preparation, Java Tutorial and Materials, Java Certification, Java Learning


March 2023 marked the latest feature release of the Java platform, which was delivered on time through the six-month release cadence as Java 20. Like Java 19, which became generally available in September 2022, Java 20 targeted JEPs—seven, in this case—from the Panama, Amber, and Loom projects.

I am using the Java 20 jshell tool to demonstrate the code in this article. To follow along, download JDK 20, fire up your terminal, check your version, and run jshell. Note that you might see a newer version of the JDK; that’s okay.

[mtaman]:~ java -version
 openjdk 20 2023-03-21
 OpenJDK Runtime Environment (build 20+36-2344)
 OpenJDK 64-Bit Server VM (build 20+36-2344, mixed mode, sharing)

[mtaman]:~ jshell --enable-preview
|  Welcome to JShell -- Version 20
|  For an introduction type: /help intro

Jshell>

Three JEPs in Java 20 are published as incubator modules to solicit developer feedback. An incubator module’s API could be altered or disappear entirely—that is, not be released in future JDK releases. Therefore, you shouldn’t use incubator features in production code. To use the incubator modules, use the --add-modules JVM flag.

The other JEPs in Java 20 are preview features. Those features are fully specified and implemented but are provided in an early release to gather feedback. You should assume that preview features will change and not use them in production code. Use the --enable-preview switch to use such features.

That’s not to say you shouldn’t use Java 20 itself in production—you should! Beyond the JEPs, there are other small improvements and fixes. The only things you shouldn’t use in production are the incubator and preview features.

In this article, I’ll describe those seven JEPs and then dive into other changes in Java 20, including changes that didn’t rise to the level of JEPs, platform enhancements, bug fixes and changes, and deprecations and removals.

The JEPs in Java 20


Seven successful JEPs made it into the Java 20 release (one more than predicted in my previous article). Scoped values are a new incubating feature, while the remaining six JEPs are resubmissions and updates of already-known incubator and preview features that appeared in Java 19 or earlier.

These JEPs are often grouped based on their long-term Java projects, as shown in Table 1.

Table 1. Seven JEPs in the Java 20 release

JEP Project Loom, which is focused on concurrency 
429 Scoped values (first incubator)
436  Virtual threads (second preview) 
437  Structured concurrency (second incubator) 
  Project Amber, for new language features
432 Record patterns (second preview) 
433  Pattern matching for switch (fourth preview) 
  Project Panama, for non-Java libraries
434 Foreign function and memory API (second preview) 
438  Vector API (fifth incubator)

Project Loom


Project Loom is designed to deliver JVM features and APIs that support easy-to-use, high-throughput lightweight concurrency (virtual threads) and new programming models (structured concurrency). New in the project is a handy construct (scoped values) that provides a thread (and, if needed, a group of child threads) with a read-only, thread-specific value during its lifetime. Scoped values are a modern alternative to thread-local variables.

Scoped values (first incubator), JEP 429. With the advent of virtual threads, the issues with thread-local variables have worsened. Thread-local variables require more complexity than is typically required for data sharing and come at a high cost that cannot be avoided.

That’s why the Java platform is now offering, in incubator form, scoped values. (For a short time, they were called extent-local variables.) They help developers move toward lightweight sharing for thousands or millions of virtual threads. JEP 429 proposes to maintain immutable and inheritable per-thread data. These per-thread variables allow for effective data sharing between child threads.

Additionally, the lifetime of per-thread variables ought to be constrained: Once the method that initially shared the data finishes, any data shared via a per-thread variable should no longer be accessible.

Virtual threads (second preview), JEP 436. Virtual threads fundamentally redefine the interaction between the Java runtime and the underlying operating system, removing significant barriers to scalability. Still, they don’t dramatically change how you create and maintain concurrent programs. Virtual threads behave almost identically to the familiar threads, and there is barely any additional API.

Java 19 introduced virtual threads to the Java platform as a first preview. In Java 20, JEP 436 provides a second preview of virtual threads in the older JEP 425 to allow time for further feedback collection. If there is no more feedback or if no significant enhancements are made to JEP 436, virtual threads will likely be a production-ready feature in the upcoming Java 21 release.

Structured concurrency (second incubator), JEP 437. The structured concurrency API aims to make multithreaded programming easier to help simplify error management and cancellation; it treats concurrent tasks operating in distinct threads as a single unit of work—improving observability and dependability.

JEP 437 essentially republishes the Java 19 version of JEP 428—to allow time to gather more feedback. The only significant change is an updated StructuredTaskScope class that supports the inheritance of scoped values (from JEP 429) by threads created in a task scope. This streamlines the sharing of immutable data across all child threads.

Project Amber


JEP 432 and JEP 433 are connected to Project Amber, which is designed to improve developers’ productivity.

Record patterns (second preview), JEP 432. Record patterns were proposed as a preview feature in Java 19’s JEP 405. Record patterns provide an elegant way to access a record’s elements after a type check. JEP 432 is a second preview that provides further refinements. The main changes since the first preview

  • Add support for an inference of type arguments for generic record patterns
  • Add support for record patterns to appear in the header of an enhanced statement
  • Remove support for named record patterns

Pattern matching for switch (fourth preview), JEP 433. Pattern matching for switch was proposed as a preview feature several times since Java 17. JEP 433 offers a fourth preview to enable the continued coevolution of pattern matching with the record patterns preview feature in JEP 432. The following are the main changes since the third preview:

  • An exhaustive switch (that is, a switch expression or a pattern switch statement) over an enum class now throws a MatchException rather than an IncompatibleClassChangeError if no switch label applies at runtime.
  • The switch label’s grammar has been simplified.
  • Inference of type arguments for generic record patterns is now supported in switch expressions and statements, along with the other constructs that support patterns.

Project Panama


This initiative, which includes JEP 434 and JEP 438, aims to improve interoperability between the JVM and well-defined “foreign” (non-Java) APIs. These APIs often include interfaces that are used in C libraries.

Foreign function and memory API (second preview), JEP 434. This JEP defines an API through which Java programs can interoperate with code and data outside the Java runtime. By efficiently invoking foreign functions that are outside the JVM—and safely accessing foreign memory that the JVM doesn’t manage—this API enables Java programs to call native libraries and process native data without the brittleness and danger of Java Native Interface (JNI).

There have been several versions of this feature since Java 17. Initially two separate APIs were proposed, but those were integrated into a single JEP in Java 19. JEP 434 incorporates the following additional refinements based on feedback:

  • The MemorySegment and MemoryAddress abstractions are unified, so zero-length memory segments now model memory addresses.
  • The sealed MemoryLayout hierarchy facilitates usage with pattern matching in the switch expressions and statements provided by JEP 433.
  • MemorySession has been split into an arena scope and a SegmentScope to facilitate sharing segments across maintenance boundaries. An arena scope provides a bounded and deterministic lifetime; it is alive from the time when a client opens an arena until the client closes the arena.

Vector API (fifth incubator), JEP 438. This API helps you express vector computations that reliably compile at runtime to optimal vector instructions on supported CPU architectures. The goal is to achieve much faster performance than with scalar computations. Versions of this API have been incubated since Java 16. This fifth version has a small set of bug fixes and performance enhancements.

Hidden gems: The most important non-JEP changes


Java 20 shipped with hundreds of performance, stability, and security improvements beyond the seven JEPs described earlier. Here are the most significant non-JEP changes.

Warnings about type casts in compound assignments with possible lossy conversions. When I ask developers about computations that involve compound assignments, many of them give a wrong answer because they don’t know that when the right-hand operand’s type in a compound assignment is not compatible with the type of the variable, a type cast is implied. Data may be lost due to possible lossy conversion.

So, for example, what is the difference between the following code lines when they are compiled?

a = a + b;
a += b;

Many Java developers will say there is no difference. However, these two operations are not always equivalent in Java. If a is a short and b is an int, the second operation will result in the following compiler error because a + b returns an int that cannot be assigned to the short variable a without an explicit cast:

LossyConversions.java:8: error: incompatible types: possible lossy conversion from int to short
               a = a + b;
                     ^

By contrast, a += b is allowed because the compiler inserts an implicit cast in a compound assignment. The statement a += b is equivalent to a = (short) (a + b), where the left 16 bits of the int result are truncated when casting to a short, resulting in the potential loss of information. The following interface will compile without any warning; however, when you run it, you may get unexpected results:

public interface LossyConversions {

       static void main(String... args) {

              short a = 30000;
              int b   = 45000;

              a += b;
              System.out.println(a);
       }
}

[mtaman]:~ javac LossyConversions.java
[mtaman]:~ java LossyConversions
    9464

Notice that the result isn’t the expected 75,000. It’s the truncated result of conversion loss: 9,464.

To help with this issue, Java 20 introduced a compiler (javac) lint option called lossy-conversions, which generates warnings about type casts in compound assignments (such as += or *=) that could result in data loss. Warnings such as the following alert developers about potentially undesirable behavior:

[mtaman]:~ javac -Xlint:lossy-conversions LossyConversions.java
LossyConversions.java:8: warning: [lossy-conversions] implicit cast from int to short in compound assignment is possibly lossy
              a += b;
                   ^
1 warning

You can silence these warnings using the @SuppressWarnings("lossy-conversions") annotation, but you shouldn’t.

New APIs for TLS and DTLS key-exchange named groups. Java 20 added two new APIs to allow customization of the named groups of key-exchange algorithms used in Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS) connections on a per-connection basis.

javax.net.ssl.SSLParameters.getNamedGroups()
javax.net.ssl.SSLParameters.setNamedGroups()

The underlying key provider may define the default named groups for each connection. However, the named groups can be customized by setting the jdk.tls.namedGroups system property or by using the setNamedGroups() method. If the system property is not null and the setNamedGroups() method is used, the named groups that are passed will override the default named groups for the specified connection.

Note that some providers may not support these new APIs. In such cases, the provider may ignore the named groups set.

New JMOD command-line option. The jmod tool lets you create JMOD archives, a new type of archive introduced in Java 9 that provides a modular way to package Java runtime components and their dependencies. Java 20 provides the --compress option, which lets you specify the compression level to use when you create a JMOD archive.

By default, the jmod tool compresses the contents of the JMOD archive using the ZIP file format. The accepted values for the --compress option are zip-0 through zip-9, where zip-0 indicates no compression and zip-9 provides the best compression. The default value is zip-6.

Why not always choose zip-9? If you are optimizing for speed of compression or decompression, you might want to choose a lower value or even no compression.

GarbageCollectorMXBean for remark and cleanup pause time in G1. The Garbage-First (G1) garbage collector now has a new GarbageCollectorMXBean called G1 Concurrent GC. This bean reports the duration and occurrence of the remark and cleanup garbage collection pauses.

These pauses occur during a complete concurrent mark cycle, increasing the collection counter by two (one for the remark pause and one for the cleanup pause), similar to the CGC field of the jstat -gcutil command. During these pauses, G1 Concurrent GC also updates the memory pool for the G1 Old Gen MemoryManagerMXBean.

Improved control of G1 concurrent refinement threads. In Java 20, the G1 garbage collector has a new controller for concurrent refinement threads; it allocates fewer threads and delays refinement to improve the efficiency of the write barrier.

As a result, the old command-line options that were used to provide parameter values for the old controller are deprecated. Specifying any of the following options on the command line will print a warning message:

-XX:-G1UseAdaptiveConcRefinement
-XX:G1ConcRefinementGreenZone=buffer-count
-XX:G1ConcRefinementYellowZone=buffer-count
-XX:G1ConcRefinementRedZone=buffer-count
-XX:G1ConcRefinementThresholdStep=buffer-count
-XX:G1ConcRefinementServiceIntervalMillis=msec

These options will be removed entirely in the future, and their use after that time will terminate the startup of the JVM.

Preventive garbage collections disabled by default. The G1 garbage collector in Java 17 introduced preventive garbage collections, thereby avoiding costly evacuation failures caused by allocation bursts when the heap was almost full.

However, those preventive collections result in additional garbage collection work because object aging is based on the number of garbage collections. This causes premature promotion into the old generation, resulting in more data and increased garbage collection work to remove these objects. The current prediction to trigger preventive garbage collections is very conservative, which triggers garbage collections unnecessarily.

In most cases, this feature is more detrimental than beneficial. Because evacuation failures are handled more efficiently, this feature has been disabled by default in Java 20. It can still be re-enabled using the command-line options -XX:+UnlockDiagnosticVMOptions and -XX:+G1UsePreventiveGC.

Unicode 15.0 support. Java 20 has been upgraded to Unicode version 15.0, which includes several updates, such as the Unicode Character Database and Unicode Standard Annexes #9, #15, and #29. For more details, see the Unicode Consortium’s release notes.

On a similar note, Java’s java.text.BreakIterator now follows the Extended Grapheme Cluster breaks defined in the Unicode Consortium’s Standard Annex #29 for character boundary analysis. This update may result in deliberate behavioral changes because the prior implementation primarily split at code point boundaries for most characters.

Also in Java 20, the locale data has been upgraded to use the Unicode Consortium’s Common Locale Data Repository (CLDR) version 42. Some noteworthy upstream changes that may impact formatting are the following:

  • The use of at for the standard date and time format has been discontinued.
  • The nonbreaking space has been prefixed to a instead of a regular space.
  • The first-day-of-the-week information for China (CN) has been fixed.
  • Japanese now supports numbers up to 9,999京.

Time zone data updated to IANA version 2023c. Version 2023c tz incorporates modifications from the 2022b and 2022a releases, which have combined various regions with identical time stamp data post-1970 into a single time zone database. While all time zone IDs remain the same, the combined time zones refer to a shared zone database.

Hidden gems: Java 20 enhancements


Optimized intrinsic features for encryption algorithms. Java 20 provides two new intrinsic features. Remember that flags controlling intrinsics require the option -XX:+UnlockDiagnosticVMOptions.

  • Java 20 provides the Poly1305 intrinsic on x86_64 platforms with AVX512 instructions. This optimizes the Poly1305 message authentication code algorithm of the SunJCE provider on x86_64 platforms using AVX512 instructions. The Poly1305 intrinsic feature is enabled by default on supporting x86_64 platforms. However, to disable this feature, you can use the -XX:-UsePoly1305Intrinsics command-line option.
  • Java 20 provides the ChaCha20 intrinsics on x86_64 and aarch64 platforms. This provides optimized intrinsic implementations for the ChaCha20 cipher used by the SunJCE provider. These optimized routines are designed for x86_64 chipsets that support the AVX, AVX2, or AVX512 instruction sets and for aarch64 chips that support the Advanced SIMD instruction set. These intrinsics are enabled by default on supported platforms, but you can disable them using the -XX:-UseChaCha20Intrinsics command-line option.

New JDK Flight Recorder events. Java 20 provides two new events.

  • jdk.InitialSecurityProperty: This event records details of initial security properties loaded via the java.security.Security class, and it contains two fields. The first is a key, which represents the security property key, and the second is a value corresponding to the security property value. This new event is enabled by default. The system property java.security.debug=properties will print initial security properties to the standard error stream with this new event and the existing jdk.SecurityPropertyModification event (which is not enabled by default). A JDK Flight Recorder recording can monitor the initial settings of all security properties and any subsequent changes.
  • jdk.SecurityProviderService: This event records details of calls made to the method java.security.Provider.getService(String type, String algorithm); its fields include the type of service, algorithm name, and security provider. It is not enabled by default but can be activated through JDK Flight Recorder configuration files or standard options.

Javadoc improvements. There are three Javadoc improvements.

  • Javadoc can now autogenerate unique ID attributes for all HTML headings in documentation comments. These IDs can be used as link anchors for easier navigation within the generated documentation. This update is especially useful for larger documents with many sections and subsections, allowing users to jump to specific content quickly.
  • The generalized {@link}, {@linkplain}, and @see tags have been improved to enable linking to specific user-defined anchors in the Javadoc-generated documentation for an element. A double hash mark (##) separates the element name from the URI fragment to distinguish these references from member references. This enhancement provides more flexibility in navigating the documentation, allowing links to be created to specific sections or headings within a document.
  • The Javadoc documentation now includes detailed information about the JEPs related to the preview features on the Preview API page. This gives you a better understanding of the origin and purpose of preview features, as you can see by comparing Figure 1 and Figure 2.

Oralce Java, Java Exam, Java Exam Prep, Java Prep, Java Preparation, Java Tutorial and Materials, Java Certification, Java Learning
Figure 1. Java 19 documentation preview list

Oralce Java, Java Exam, Java Exam Prep, Java Prep, Java Preparation, Java Tutorial and Materials, Java Certification, Java Learning
Figure 2. Java 20 documentation preview list

New constructors for the java.security.InvalidParameterException class. Two new constructors— InvalidParameterException(String, Throwable) and InvalidParameterException(Throwable)—simplify the creation of InvalidParameterException objects with a cause.

Methods for converting to and from half-precision floating-point format. Two new methods, java.lang.Float.floatToFloat16 and java.lang.Float.float16ToFloat, can convert to and from the IEEE 754 binary16 half-precision format. However, when the JIT compiler optimizes these methods, they may return different NaN (not a number) results.

To prevent the JIT compiler from optimizing these methods, use the following command-line options:

-XX:+UnlockDiagnosticVMOptions -XX:DisableIntrinsic=_floatToFloat16,_float16ToFloat

Hidden gems: Java 20 bug fixes and related changes


Java XSL Template limitations. Suppose you use the JDK’s XSLT processor to convert stylesheets to Java objects. If the XSL template is too large, you may encounter an exception.

com.sun.org.apache.xalan.internal.xsltc.compiler.util.InternalError: Internal XSLTC error: a method in the translet exceeds the Java Virtual Machine limitation on the length of a method of 64 kilobytes. This is usually caused by templates in a stylesheet that are very large. Try restructuring your stylesheet to use smaller templates.

To avoid this issue, you can restructure your stylesheet to smaller templates or use third-party JAR files in the classpath to override the JDK’s XSLT processor.

Changes to the java.net core libraries. The following four changes were made:

  • HTTP response input streams will throw an IOException upon interrupt. When you use the ResponseSubscribers::ofInputStream method, it returns an InputStream instance known as the HTTP response input stream. In Java 20, the default implementation of the read method in these streams has been changed.
    • Previously, if the thread performing the read operation was interrupted, the interruption was ignored. But now, if the same thread is interrupted while blocking the read operation, the request will be canceled; the input stream will be closed; the thread’s interrupt status will be set to true; and an IOException will be thrown.
  • URL constructors called with malformed input may throw a MalformedURLException in cases where it was not thrown previously. In Java 20, how input is parsed in the URL constructors has become stricter. If you call these constructors with input that is not correctly formatted, you may get a MalformedURLException exception thrown that wouldn’t have been thrown before.
    • Previously, some validation and parsing checks were done when the URL::openConnection or URLConnection::connect methods were called. Now, these checks are done earlier, within the URL constructors themselves. This means that if there’s an issue with the URL’s formatting that would have previously been delayed until the connection was opened, it may now cause a MalformedURLException to be thrown when the URL is constructed.
    • This change affects only URLs that use the JDK’s built-in stream handler implementations. URLs using third-party implementations are not affected. To return to the previous behavior, set the system property -Djdk.net.url.delayParsing=true when you run your program. Note that this property is provided for backward compatibility and may be removed in the future.
  • The default timeout value for idle connections created by java.net.http.HttpClient has been changed in Java 20. The HttpClient default keep-alive time is now 30 seconds. This means that if a connection remains idle for 30 seconds, it will be closed by the HttpClient. Previously, the default timeout value for HTTP/1.1 and HTTP/2 connections was 1,200 seconds.
  • Java 20 introduces idle connection timeouts for HTTP/2. The jdk.httpclient.keepalivetimeout property can be used to set a systemwide value in seconds to close idle connections for HTTP/1.1 and HTTP/2 when the HttpClient is used. Additionally, you can use the jdk.httpclient.keepalivetimeout.h2 property to set a timeout value exclusively for HTTP/2, regardless of whether the jdk.httpclient.keepalivetimeout property is set at runtime.

Changed behavior for java.math.BigDecimal.movePointLeft() and java.math.BigDecimal.movePointRight(). Suppose you use these two methods with an argument of zero on a negative scale target. With Java 20, the methods will return a result numerically equivalent to the target but with a different unscaled value and scale.

In earlier releases, these methods returned a result with the same unscaled value and scale, which did not align with the specification. However, this change applies only to cases where the target has a negative scale and the argument is zero; in other cases, the behavior remains the same.

Object identity used for IdentityHashMap’s remove and replace methods. IdentityHashMap is a unique type of map that considers keys to be equal only if they are identical. A comparison using the == operator returns true rather than the equals() method.

However, when the default remove(Object key, Object value) and replace(K key, V oldValue, V newValue) methods were added to the Map interface in Java 8, they were not overridden in IdentityHashMap to use == instead of equals(), leading to a bug. That has been fixed in Java 20.

FileChannel positional write unspecified in APPEND mode. The Java specification for java.nio.channels.FileChannel has been updated to provide clarity on the behavior of the FileChannel::write(ByteBuffer, long) method when an attempt to write to a specific position is performed.

When java.nio.File.StandardOpenOption.APPEND is passed to FileChannel::open when a channel is opened, the behavior of writing to a specific position is system-dependent. At the same time, the channel is opened in append mode.

This means that the outcome can differ depending on the operating system. In some operating systems, bytes will be written to the specified position. In other operating systems, the given position will be ignored, and the bytes will be appended to the end of the file.

Filenames for Unicode Normalization Format D (NFD) not normalized on macOS. The normalization of filenames for Apple’s NFD version has been changed on macOS. Previously, filenames were normalized to NFD on HFS+ prior to macOS 10.13. However, this normalization is no longer performed on the APFS file system on macOS 10.13 and later.

If you want to revert to the previous behavior and normalize filenames to NFD, set the system property jdk.nio.path.useNormalizationFormD to true.

HelloVerifyRequest messages used for DTLS resumption. In Java 20, a fix was made to the SunJSSE DTLS implementation that enables the exchange of cookies for all handshakes, including new and resumed handshakes, by default. You can disable this feature for resumed handshakes by setting the system property jdk.tls.enableDtlsResumeCookie to false. This property affects only the cookie exchange for resumed handshakes.

Improved enum switches. Java 20 has a change related to switch expressions over an enum type. When you enable preview features with --enable-preview, if the selector expression produces an unexpected enum constant value, a MatchException will be thrown instead of an IncompatibleClassChangeError.

This situation can occur only if a new enum constant is added to the enum class after the switch has been compiled.

FXML JavaScript engine disabled by default. Starting with Java 20, the JavaScript script engine for FXML is disabled by default, and an exception will be thrown when loading .fxml files that contain a JavaScript processing instruction (PI).

To enable this feature if your JDK has a JavaScript script engine, set the system property -Djavafx.allowjs=true.

JMX connections use ObjectInputFilter by default. For Java 20, the default Java Management Extensions (JMX) agent has been updated to restrict the types the server will deserialize using an ObjectInputFilter on the remote method invocation (RMI) connection. This should be fine for the normal usage of MBeans in the JDK. However, if applications have registered their MBeans in the platform’s MBeanServer, they may need to extend the filter to include any additional types their MBeans accept as parameters.

The filter pattern is set in the JDK/conf/management/management.properties file using the property com.sun.management.jmxremote.serial.filter.pattern. The default filter covers any type that OpenMBeans and MXBeans might use.

The serialization filtering and the filter pattern format are described in detail in the Java Core Libraries Developer Guide. If any additional Java types need to be passed, the default filter can be overridden by using the -Dcom.sun.management.jmxremote.serial.filter.pattern= option.

Hidden gems: Java 20 deprecation and removals


Methods changed to throw an UnsupportedOperationException. Java 20 removes the ability to suspend or resume a thread using the Thread.suspend() and Thread.resume() methods. Those methods were prone to deadlock and have been deprecated since JDK 1.2. They have been changed to throw an UnsupportedOperationException.

Similarly, the ability to stop a thread using the Thread.stop() method has been removed due to its unsafe nature of causing a java.lang.ThreadDeath exception.

java.net.URL constructors deprecated. As of Java 20, you should use java.net.URI for parsing or constructing URLs. Suppose an instance of java.net.URL is still needed to open a connection. In that case, java.net.URI can construct or parse the URL string by calling URI::create() and then calling URI::toURL() to create the URL instance.

For example, here’s the old code.

URL url = new URL("https://blogs.oracle.com/authors/mohamed-taman");

And here’s the new code.

URL url = URI.create("https://blogs.oracle.com/authors/mohamed-taman").toURL();

When a custom stream handler is required to construct a URL, use the new URL::of(URI, URLStreamHandler) methods.

JMX management applets deprecated for removal. The JMX management applet (m-let) feature is no longer useful for modern applications and will be removed in a future release. The following public classes are being deprecated and will no longer be supported: MLet, MLetContent, PrivateMLet, and MLetMBean.

This deprecation will not affect the JMX agent used for monitoring the built-in instrumentation of the JVM or any tooling that relies on JMX.

Thread text removed from Subject.current. In Java 20, the Subject.current specification has been changed. Previously, the Subject was expected to be inherited when a new thread was created. However, this expectation has been dropped, and the Subject is now stored in the AccessControlContext. The AccessControlContext is inherited when platform threads are created but not for virtual threads, because they do not capture the caller context at thread creation time.

DTLS 1.0 and TLS-ECDH_* disabled. In Java 20, the DTLS 1.0 protocol has been disabled by default due to its weak security and lack of support for strong cipher suites. To achieve this, DTLS 1.0 has been added to the jdk.tls.disabledAlgorithms security property in the java.security configuration file. An attempt to use DTLS 1.0 will result in an SSLHandshakeException.

You can re-enable this protocol at your own risk by removing DTLSv1.0 from the jdk.tls.disabledAlgorithms security property.

Similarly, the TLS_ECDH_* cipher suites have been disabled by adding ECDH to the jdk.tls.disabledAlgorithms security property in the java.security configuration file. These cipher suites lack forward-secrecy and are hardly ever used in practice. While some of them were already disabled due to the use of disabled algorithms such as 3DES and RC4, the rest of them have now been disabled. Any attempts to use cipher suites starting with TLS_ECDH_ will result in an SSLHandshakeException.

You can re-enable these cipher suites at your own risk by removing ECDH from the jdk.tls.disabledAlgorithms security property.

Error thrown if the default java.security file fails to load. Previously, when the default security configuration file conf/security/java.security failed to load, the JDK would fall back to using a hardcoded default configuration. However, in Java 20, if the default configuration file fails to load, the JDK throws an InternalError instead of using the hardcoded default configuration.

This change is intended to make misconfigurations more obvious and avoid potential security issues caused by unexpected behavior.

Conclusion

There’s a lot more to a new Java release than the widely publicized JEPs. Study all these changes; even if you don’t use the preview and incubator features, there are sufficient bug fixes and other enhancements to make Java 20 worth testing and using on production systems today.

Source: oracle.com

Wednesday, May 10, 2023

Quiz yourself: Nested lambdas and Java thunks

Quiz Yourself, Oracle Java, Oracle Java Career, Oracle Java Skills, Oracle Java Jobs, Oracle Java Learning, Oralce Java Preparation


Given the following three functional interfaces

interface Woo { public void wow(); }
interface Moo { public String mow(); }
interface Boo { public int bow(); }

and a class fragment

static void doIt(Woo w) {
  System.out.print("W");
  w.wow();
}
static String doIt(Moo m) {
  System.out.print("M");
  return m.mow();
}
static int doIt(Boo b) {
  System.out.print("B");
  return b.bow();
}
public static void main(String[] args) {
  doIt(() -> { doIt(() -> "done"); });
  doIt(() -> { doIt(() -> 1); });
}

What is the result? Choose one.

A. MMBB is printed.
B. WWWW is printed.
C. WMWB is printed.
D. MWBW is printed.
E. Compilation fails in the main method because the code is ambiguous.

Answer. This question investigates how lambdas take on a type that is compatible with their code and the context in which they are declared.

In this question, you are presented with several lambdas, including nested lambdas. That’s certainly a recipe for hard-to-read code, but the rules don’t change from the simple case. Look at these lambdas in turn and notice which interface each might be compatible with.

The two lambdas that are nested inside the others are () -> "done" and () -> 1.

It is clear that the first of these is compatible with the interface Moo because the method it defines takes no arguments and returns a String. Further, that first lambda is not compatible with either of the other interfaces because you can’t cast or promote a String to an int.

The second lambda is compatible with Boo because it declares a method that takes no arguments and returns an int. Again, casting or promotions can’t reconcile that lambda with either of the other two interface types.

Next, look at the outer lambdas: () -> { doIt(() -> "done"); } and () -> { doIt(() -> 1); }.

You know that the return type of the first enclosed lambda is String and that of the second is int. However, notice that these enclosing lambdas are block lambdas, that is, they include curly braces and must, therefore, define entire method bodies. However, the method bodies do not include return statements. So, in both cases, the enclosing lambda will invoke the enclosed lambda and ignore the returned value. The enclosing lambdas, therefore, have a void return type; as such, both are instances of the Woo interface.

At this point, you should have an instinct that since you have lambdas implementing all three interfaces, the output should contain all three letters: M, B, and W. If that instinct is correct, you can conclude that options A and B are looking unlikely. But that’s just a gut feeling, and while you might let that guide you if you’re running short of time in the exam, let’s trace the execution to determine what actually happens.

Think about the order in which the enclosing and enclosed lambdas bodies actually execute. In a Java method call, the arguments to a method invocation are always evaluated before the method is actually called. However, the value of a lambda is an object that contains the method the lambda describes. Java does not execute that method in constructing that object, and that means that when a lambda is passed as an argument, the method represented by the lambda has not been executed prior to the actual invocation of the method to which the lambda is being passed.

From this, you can tell that the very first lambda to be executed will be a Woo, which will print W. That one then delegates to the String-producing Moo and prints M. The process then repeats with a W from the second line’s enclosing lambda, followed by a B from the enclosed lambda that’s a Boo type. That results in the output WMWB.

Of course, the code compiles and runs, since all the lambdas validly and unambiguously satisfy one or other of the functional interfaces. Therefore, option E is incorrect. From the previous paragraph, you should conclude that the output is WMWB. Therefore, the correct answer is C and options A, B, and D are incorrect.

As a side note, you can use this technique to create the effect of lazy execution. When an exception is logged, for example, it is often quite computationally intensive to traverse all the frames in a stack, collect data, and concatenate the data into a log message—and very often, that log message is never used because the filtering level abandons it.

Java’s logging APIs allow passing a Supplier<String> to the log methods, so that if the message will not be used, it need never be evaluated. This idea is a design pattern that has a curious name; the pattern is commonly called a thunk. (It’s like the cartoonish past tense of to think, as in, “I had a thought, and the thought that I thunk was …” It’s very silly; don’t ask us why it exists, because we don’t know!)

This technique is sometimes implemented by a language (for example, Scala) such that the programmer simply writes a block of code that is wrapped in a thunk before being passed into an as-yet-unexecuted method. This is typically described as passing parameters by name.

It’s also interesting to consider what would have happened if the code had included explicit return statements.

public static void main(String[] args) {
  doIt(() -> { return doIt(() -> "done"); });
  doIt(() -> { return doIt(() -> 1); });
}

In this form, all four lambdas would return a value. The two in the first line return String and are, therefore, instances of Moo. The second two return int and are instances of Boo. The modified code would then print MMBB.

Conclusion. The correct answer is option C.

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


Friday, May 5, 2023

Reflection for the modern Java programmer

Take advantage of the rich metadata the Java compiler provides to the JVM.


Reflection is one of the most flexible and powerful techniques Java developers have at their command. However, it is frequently misunderstood or misused, and certain aspects of its API and design are rather clunky and, in places, out of date.

Java Programmer, Oracle Java Career, Oracle Java Jobs, Oracle Java Prep, Oracle Java Preparation, Oracle Java Tutorial and Material, Oracle Java

Nevertheless, to become an advanced Java developer, gaining a solid understanding of reflection (also known as the Core Reflection API) is an essential step. Let’s start with an important point of history that sometimes goes unnoticed.

When Java was first released over 25 years ago, the two most dominant programming languages were probably C and C++. As most Java developers know, the syntax of Java is heavily influenced by these languages, especially C++. However, despite the similar syntax, as programming environments go, Java is very different from C++. The nature of this difference is in how programs are built and executed.

◉ C++ compiles to a native machine code binary that can be directly executed.
◉ Java compiles to a portable, intermediate format (bytecode) that requires a runtime container to execute.

The Java runtime execution container, that is, the Java Virtual Machine, provides external support for certain concerns that Java programs delegate to the runtime. The usual example of these concerns is garbage collection: The Java programmer has no control over allocating and recovering memory within the heap. Instead, the reclamation process is under the complete control of the JVM.

This is an example of what is meant by describing the JVM as a managed environment. Java programmers give up control of the precise low-level details of handling memory and in return the JVM manages those (boring, pedantic, exacting) details on their behalf.

When it comes to reflection, there is another aspect of the JVM’s managed execution environment to focus on: the rich runtime type information that is present in every executing Java program. Consider a piece of code like the following:

List<String> ls = getSomeStrings();
System.out.println(ls.size());

Without knowledge at runtime of the inheritance and interface implementation hierarchy of the program, how can the JVM work out which size() method to call? After all, the List interface does not even contain a method implementation.

The details of the hierarchy are contained in the class files because each individual class refers to the types it depends upon. The JVM, during the class loading process, assembles this data into a representation of a graph that describes the class inheritance and interface implementation of every type in the system. This includes JDK types, third-party libraries, and custom, user-defined classes of the application.

This inheritance metadata is always present for every type, and nothing a Java programmer can do will prevent it from being created and available at runtime. This stands in contrast to C++, which by default does not preserve type metadata until runtime. Instead, C++ uses keywords such as virtual and override to allow the programmer to selectively opt in to limited runtime behavior (such as method overriding).

In Java the runtime metadata is always present on every object, although the exact details of that metadata will depend upon the JVM implementation. For the rest of this article, when I’m talking about internals, I will discuss only the HotSpot JVM, which is the implementation used by OpenJDK and Oracle’s JDK.

A discussion of the Core Reflection API is, however, completely applicable to any JVM implementation, as the API is part of the java.* namespace and thus is a standard.

In the HotSpot JVM, the metadata is held in an object header, which separates the metadata into type-specific and instance-specific metadata.

The instance-specific metadata is known as the mark word, and it is used to keep track of various important pieces of information such as whether an object’s intrinsic lock (also known as the monitor or synchronization lock) is being held by a thread. The metadata also contains information used during garbage collection (which is where the name comes from, after the concept of mark sweep garbage collection).

On the other hand, the shared, type-specific metadata is stored in an area called metaspace and every Java object header contains a pointer to the metadata for the class that the object belongs to. This is known as the klass word of the header.

The fact that every object has a pointer to the shared class metadata means that during code execution, the JVM can always traverse the pointer and access the runtime type information of the class that the object belongs to.

Therefore, one way of thinking about reflection is that it’s posing the following question: “What if the execution environment exposed the runtime metadata (which is guaranteed to exist) and allowed Java programmers to access and use it during the execution of their programs?”

A brief history of reflection


In the first version of Java, the Core Reflection API did not exist, but there were already methods on java.lang.Class for accessing some metadata about the runtime types of objects, such as the name of the class. For example, code such as the following would already work in Java 1.0:

Object o = getObject();
System.out.println(o.getClass().getName());

The next release of Java, version 1.1, introduced full reflection capabilities for the first time. The package java.lang.reflect became part of the standard distribution and allowed developers to access a full range of reflective operations. This included such things as calling methods on objects of unknown type that had been reflectively created.

Note that the Core Reflection API predates the arrival of Java’s Collections API (which appeared in Java 1.2). As a result, types such as List do not appear in the Core Reflection API; instead, arrays are used. This makes some parts of the API rather more awkward to use than they otherwise would be.

Of course, Java was not the first language to provide reflective programming capabilities; others, such as Smalltalk, had pioneered the reflective approach. However, Java was the language that really brought reflective programming into mainstream software development.

The Java ecosystem warmly embraced reflection, and many of the most popular and powerful Java frameworks rely upon these capabilities to implement their core functionality.

Reflection in action


Here are some tasks you can do with reflection.

◉ Create an object reflectively.
◉ Locate a method on the object.
◉ Call the located method.

Those tasks are accomplished with code such as the following. In this first example, exception handling is ignored, for now, to improve code clarity.

Class<?> selfClazz = getClass();
Constructor<?> ctor = selfClazz.getConstructor();
Object o = ctor.newInstance();
Method toStr = selfClazz.getMethod("toString");
Object str = toStr.invoke(o);
System.out.println(str);

The code starts from a class object, which you have obtained via getClass() in this example. In practice, this class object can also be obtained from a class loader or, more rarely, as a class literal (such as String.class). Reflection can be combined with class loading to provide the capability for Java programs to work with code that was completely unknown at compile time.

This approach is the basis of plugin architectures and other very dynamic mechanisms that Java can make use of.

No matter how you obtained it, once you have a class object, you may call getConstructor() on it. This method is variadic—that is, it may take a variable number of arguments—to cope with the possibility of multiple constructors for the class.

The example above calls the default (void) constructor; this constructor takes no arguments. The result is a constructor object from which you can obtain an instance of the class, just as if you had called the constructor directly.

From the constructor object, calling newInstance() causes a new object of the appropriate type to be created. This method is also variadic, because in the nondefault case you would need to supply the correct parameters to the call.

By the way, the class object also declares a newInstance() method that can be used to create new objects directly, without creating an intermediate constructor object. (Class.newInstance() is deprecated due to its exception handling behavior; this will be discussed shortly.)

You may also call getMethod() to retrieve a method object from the class object. This represents the capability to call a method that belongs to the class that the method object was created from. Once you have the Method object, you can use invoke() to call the method it represents.

Instance methods need to be called reflectively with the receiver object (the object that the method is being called upon) as the initial argument to invoke(), with any method parameters following. In the example above, o is the receiver and there are no parameters for the call to toString().

It is also possible to access fields reflectively; the API and syntax are very similar to the case of methods. The core class is java.lang.reflect.Field and the fields can be accessed via Class.getField() and other similar methods. Due to the similarity of the code, I’ll skip over this case.

Variadic methods in the Core Reflection API


Here’s a second and slightly more complex example, where the constructors and method calls require arguments.

public record Person(String firstName, String lastName, int age) {}

Class<?> selfClazz = Class.forName("javamag.reflection.ex1.Person");
Constructor<?> ctor = selfClazz.getConstructor(String.class, String.class, int.class);
Object o = ctor.newInstance("Robert", "Smith", 63);
Method toStr = selfClazz.getMethod("equals", Object.class);
Object other = new Object();
Object isEqual = toStr.invoke(o, other);
System.out.println(isEqual);

Here, the calls to getConstructor() and getMethod() are provided with a number of class objects. These represent the type signature of the constructors or methods.

The calls to Constructor.newInstance() and invoke() are, similarly, provided with objects that are the parameters to the calls. These parameters should, of course, match the expected types of the arguments.

In addition to this requirement to use variadic methods to get reflection objects, the type system and nature of Java come in to play in several places within the reflective APIs. These concerns include

◉ Dealing with exceptions
◉ Overloading
◉ Access control
◉ Autoboxing and casting

Each of these concerns can complicate the writing of reflective Java code.

Dealing with exceptions. Until now, I’ve ignored the need to handle exceptions within the reflective code examples. To move into the real world, I’ll show part of the first example with exception handling code, as follows:

try {
    Class<?> selfClazz = getClass();
    Constructor<?> ctor = selfClazz.getConstructor();
    Object o = ctor.newInstance();
    // ...
} catch (NoSuchMethodException e) {
    // Could be thrown by getConstructor()
    e.printStackTrace();
} catch (IllegalAccessException e) {
    // Could be thrown by newInstance()
    e.printStackTrace();
} catch (InstantiationException e) {
    // Could be thrown by newInstance()
    e.printStackTrace();
} catch (InvocationTargetException e) {
    // Could be thrown by newInstance()
    e.printStackTrace();
}

Of these four possible exceptions, NoSuchMethodException can be thrown by getConstructor() if the class does not have a constructor with a signature that matches the class objects passed in. The remaining exceptions could be thrown by Constructor.newInstance() as follows:

◉ IllegalAccessException: Access control is enforced, and the caller does not have the correct access.

◉ InstantiationException: The constructor can be accessed, but an object cannot be created, for example, because the underlying class is abstract.

◉ InvocationTargetException: The execution of the constructor body threw an exception.

The final exception, InvocationTargetException, contains a reference to the original exception that was thrown by the constructor body, and this underlying exception can be retrieved by calling getTargetException(). It is this feature that explains why this approach is preferred over the deprecated Class.newInstance() method: The deprecated method has no way to retrieve the underlying exception.

Of the remaining reflective calls in this example, getMethod() can throw NoSuchMethodException, and invoke() can throw IllegalAccessException and InvocationTargetException.

Overloading. The existence of method overloading in the Java language means that when a method (or constructor) is looked up via getMethod(), passing the name is not sufficient; instead, the method signature, represented as a sequence of class objects (which will be converted to a Class[]) must also be provided.

I consider usage of Class[] (and Object[] when a method is called reflectively) as something of a design flaw, and it does make the API more difficult to work with. This is really just an unfortunate accident of history because the Core Reflection API was added before the Collections libraries existed, so the API must be maintained in its original form for backwards compatibility reasons.

Access control. Another problem relates to access control: The API provides two different methods, getMethod() and getDeclaredMethod(), to access methods reflectively.

The first of these two possibilities, getMethod(), is used for looking up public methods. By contrast, getDeclaredMethod() can be used to find any method declared on a class, even private methods.

By default, reflective code still respects the access control semantics of the Java language, but it is possible to override those semantics because the API provides the setAccessible() method, which can be called on methods, constructors, and fields. Once this method has been called on an accessible object, the access control modifiers will be ignored.

In my opinion, the setAccessible() method is fundamentally dangerous, because it allows programmers to selectively turn off parts of the access control system when they are working reflectively.

It represents a compromise in the reflective subsystem: Sometimes there is just no other way to get the access that is required, but when it’s overused it can cause all sorts of security and safety issues.

It can be argued that the compromise is not too bad, because by the time you have obtained a Method object or another accessible object, the corresponding class has definitely already been loaded, which means that the bytecode of the class has already passed verification by the time you make your reflective call.

Nevertheless, using setAccessible() to gain access to methods that are otherwise inaccessible represents a violation of encapsulation and a usage that the original code author did not intend.

Autoboxing and casting. The Core Reflection API contains several variadic methods for lookup and invocation. For example, Constructor.newInstance() and invoke() both take Object... as their variadic parameter, and this immediately raises the question of what to do about primitive values when you call code reflectively.

First off, the lookup methods (such as getConstructor() and getMethod()) take Class<?> objects as parameters, so you can simply pass the class literals corresponding to primitive types, such as int.class.

It’s worth noting here that almost all class literals are singletons. For example, String.class is the only instance of the type Class<String>. However, there are two instances of Class that are parameterized by each wrapper type (such as Integer): one for the wrapper type and one for the corresponding primitive.

That is, the following code prints false:

Class<Integer> intClz = int.class;
Class<Integer> integerClz = Integer.class;
System.out.println(intClz == integerClz);

The second example above provided a clue as to how this applies to reflective code in practice.

Class<?> selfClazz = Class.forName("javamag.reflection.ex1.Person");
Constructor<?> ctor = selfClazz.getConstructor(String.class, String.class, int.class);
Object o = ctor.newInstance("Robert", "Smith", 63);

This code looks up the primary constructor for the record type and then instantiates an object by passing a primitive value as the second argument. This argument will need to be boxed to match the signature of Constructor.newInstance() (which takes Object[]), so the call is really the following:

Object o = ctor.newInstance("Robert", "Smith", Integer.valueOf(63));

The final argument will then be unboxed in the reflection implementation code prior to the actual call to the constructor being made. This approach is fairly simple once you get used to it, but it is a little clumsy, and it does require extra unnecessary boxing operations.

The return value of reflective calls also requires careful handling; the return type of invoke() is Object, so any return value needs to be downcast to an expected, more useful, type.

This cast operation, of course, may fail with a ClassCastException (which is a runtime exception).

Source: oracle.com

Wednesday, May 3, 2023

Quiz yourself: Collecting the Java garbage


Given the following

class Car implements Serializable {
  class Engine implements Serializable { }
  private Engine e;
  public void Car() {
    e = new Engine();
  }
  public Engine getEngine() { return e; }
}

and

List<Serializable> l = new ArrayList<>();
var c = new Car(); // line n1
var e = c.getEngine();
l.add(0, c);
l.add(0, e);
c = null; // line n2
l.set(1, 1); // line n3
e = null; // line n4
l.set(0, 0); // line n5

When will the Car instance created at line n1 become eligible for garbage collection? Choose one.

A. After line n2
B. After line n3
C. After line n4
D. After line n5
E. After the code throws an exception and leaves the method containing this fragment

Answer. This question investigates some foundational knowledge for understanding garbage collection and the idea of reachability. Depending on your expertise and attention to detail, you might be surprised that this question does not investigate inner classes in any depth. The question also investigates methods that break naming conventions and the declaration of constructors as well as the behavior of some of the methods of java.util.List.

Quiz Yourself, Java Garbage, Oracle Java Certification, Oracle Java Prep, Java Preparation, Java Career, Java Skills, Java Jobs, Java Preparation

Look at how the code executes by starting from the top of the first fragment shown.

First, the code instantiates an ArrayList and stores a reference to it in the variable l, which is of type List<Serializable>. Because both the Car and Engine classes are serializable, the list will accept both these types if you try to add them.

Next, the code creates a Car object. Soon the code will add it to the list at position zero. However, look at the construction of the Car. At a quick glance, you might assume that the construction uses the following constructor:

public void Car() {
    e = new Engine();
  }

Look closer: This is not a constructor. It’s a method with an egregiously poor name!

It’s likely that it was intended to be a constructor, but somebody’s fingers typed void. A constructor must not declare a return type and must have a name that exactly matches the class to be initialized. Therefore, if you provide a return type, as is the case with this code, you have defined a method that simply happens to have the same name as the class.

At this point, you might be throwing your hands up in horror exclaiming that this is a stupid trick question. Well, we sympathize; when we first saw this, we were pretty frustrated too, and we had a bit of a discussion about how “the smart people will get it wrong” (because if that had been a constructor, all kinds of cool things would happen and that makes test takers expect the question to be about those cool things). This, we protested, would make the question a negative discriminator. That’s test-writer jargon for a question that test takers are more likely to get wrong if they’re better prepared or smarter.

However, the reality is twofold.

◉ This kind of error will not be flagged by the compiler (it also doesn’t cause a warning with the default configuration of IntelliJ IDEA), and the resulting code will run too, albeit with the wrong results. Therefore, if you were to make this mistake, finding it and fixing it will be you and the debugger against the world.

◉ The second point is that attention to detail always matters with programming. You might protest “but you knew what I meant” to a human, but that won’t ever cut it with a compiler.

So, with that clarified, continue the investigation.

In view of the above, you now know that the constructor that initializes the Car is actually the default constructor. Importantly, this will do nothing to initialize the field Engine e, so it remains at the default value of null.

After the construction of the Car, the code calls c.getEngine(). This call returns null, and that’s stored in the variable e.

The next two steps call add(0, _). This two-argument version of add stores values at the given index, moving items to the higher index positions as needed to make space. In this case, the items are added to the very front of the list. At this point, you have a null in position zero and a Car in position one. The Car started at position zero but was pushed down to make space for the null.

Note that some lists throw an exception when a null value is added, but this is not the case with an ArrayList, so option E is looking unlikely.

At this point, variable c refers to the Car object and so does index one of the list. No Engine objects have been created, but position zero of the list and the variable e both contain null.

Line n2 overwrites the variable c with null. At this point, the only reference left to the Car object is at index one of the list. Because a reference remains, the object is not yet eligible for garbage collection. Therefore, option A is incorrect.

Line n3 overwrites the reference stored at index one of the list. The value written is the number 1. This is permitted, because it will be autoboxed to an Integer that implements Serializable. Again, no exception is thrown, so option E still looks unlikely.

Immediately after this set operation, there are no references left to the Car object. That makes it eligible for garbage collection. That tells you the correct answer to this question is option B and options C, D, and E are incorrect.

Line n4 writes null over the existing null in variable e. Line n5 changes the null at index zero of the list to the number 0. This is permitted and does not throw an exception.

To recap, no exceptions are thrown, and the Car object is eligible for garbage collection after line n3. This confirms that the correct answer is option B and not option E.

Conclusion. The correct answer is option B.

Source: oracle.com

Monday, May 1, 2023

What is Java SE?

Java SE, Oracle Java Certification, Oracle Java Prep, Oracle Java Learning, Oracle Java Guides, Oracle Java Tutorial and Materials

Java SE, also known as Java Standard Edition, is a widely used platform for developing and deploying Java-based applications. It provides a comprehensive set of APIs, tools, and runtime environments that allow developers to build and run Java applications on various platforms.

Overview of Java SE

Java SE is a platform that consists of three main components: the Java Development Kit (JDK), the Java Runtime Environment (JRE), and the Java Virtual Machine (JVM).

Java Development Kit (JDK)

The Java Development Kit (JDK) is a software development kit used to develop Java applications. It contains tools and libraries necessary for developing, debugging, and monitoring Java applications. The JDK includes a Java Virtual Machine (JVM), which is responsible for executing Java code. It also includes the Java compiler, which converts Java source code into bytecode that can be executed by the JVM. Additionally, the JDK includes various tools such as the Java debugger, profiler, and documentation generator. The JDK is available for multiple platforms such as Windows, macOS, and Linux.

Java Runtime Environment (JRE)

The Java Runtime Environment (JRE) is a software package that provides the runtime environment necessary to run Java applications. It contains the Java Virtual Machine (JVM), which is responsible for executing Java bytecode, as well as the core Java class libraries. The JRE does not contain the tools and libraries needed for Java application development, which are provided by the Java Development Kit (JDK).

The JRE is available for multiple platforms, including Windows, macOS, and Linux, and can be downloaded and installed independently of the JDK. Many computers already have a JRE installed, as it is often included with web browsers or other software that requires Java. When a Java application is run, the JRE is responsible for interpreting and executing the bytecode, providing the necessary resources for the application to run properly.

Java Virtual Machine (JVM)

The Java Virtual Machine (JVM) is a software program that provides a runtime environment for Java applications to run. It acts as an interpreter for Java bytecode, which is a compiled version of Java source code. When a Java application is executed, the JVM reads the bytecode and translates it into machine-specific code that can be executed on the computer's processor.

The JVM provides several features, including memory management, garbage collection, and security. It dynamically allocates memory for Java objects as needed, and the garbage collector automatically frees up memory when it is no longer being used. Additionally, the JVM provides a layer of security by enforcing Java's access control and ensuring that applications cannot access resources outside of their designated permissions.

The JVM is available for multiple platforms, including Windows, macOS, and Linux, and is a core component of the Java Runtime Environment (JRE) and Java Development Kit (JDK). The JVM is an essential part of the Java platform, providing the ability to write platform-independent code that can run on any device with a JVM implementation.

Features of Java SE

Java SE provides various features that make it a popular platform for developing and deploying Java applications. Some of these features include:

Object-oriented programming

Java SE is an object-oriented programming language, which means that it allows developers to create classes and objects that encapsulate data and behavior.

Platform independence

Java SE is a platform-independent platform, which means that Java applications can run on different platforms without any modifications.

Security

Java SE provides a secure environment for running Java applications by using a sandboxing mechanism that restricts the access of Java applications to system resources.

Automatic memory management

Java SE includes automatic memory management, which means that developers do not have to manage memory manually. The JVM automatically allocates and deallocates memory for Java applications.

Multithreading

Java SE provides support for multithreading, which allows Java applications to perform multiple tasks simultaneously.

Benefits of Java SE

Java SE provides various benefits for developers and organizations that use it for developing and deploying Java applications. Some of these benefits include:

Reduced development time

Java SE provides a comprehensive set of APIs and tools that allow developers to build Java applications quickly and efficiently.

Portability

Java SE allows Java applications to run on different platforms, which makes it easier for organizations to deploy Java applications in different environments.

Security

Java SE provides a secure environment for running Java applications, which helps organizations protect their sensitive data and assets.

Scalability

Java SE allows developers to build scalable Java applications that can handle large amounts of data and traffic.

Conclusion

Java SE is a widely used platform for developing and deploying Java applications. It provides a comprehensive set of APIs, tools, and runtime environments that allow developers to build and run Java applications on different platforms. Java SE provides various features and benefits that make it a popular platform for developing and deploying Java applications.