Friday, February 18, 2022

Java’s new HTTP Client API, UNIX-domain sockets, and Simple Web Server

Oracle Java Certification, Java Certification, Oracle Java Skills, Oracle Java Prep, Java Learning, Oracle Java API, Oracle Java Tutorial and Material

A conversation with two architects about new networking features in the JDK, including a new feature in Java 18

Download a PDF of this article

Networking capabilities were built into Java’s core libraries right from the start. For developers, the fact that networking was built into a major development platform was a welcome change, because organizations didn’t have to rely on third-party libraries or invest resources to create their own low-level networking code to support common networking protocols.

Of course, in the 1990s, not every application needed networking—but now networking support is an absolute requirement for nearly all platforms. Indeed, most of today’s most popular business and consumer applications wouldn’t exist without the ability to connect to services using the HTTP protocol.

That said, Java’s very widely used HTTP Client API hadn’t seen a significant improvement until the new API started to incubate in JDK 9 as JEP 110 and was made standard in JDK 11 as JEP 321.

David Delabassée, a developer advocate in the Java Platform Group at Oracle, recently spoke with Oracle Java architects Daniel Fuchs and Michael McMahon to discuss some of the recent network updates to the Java platform. Here are a few of the highlights from that conversation.

Delabassée: Why was the new Java HTTP Client API introduced?

McMahon: The original, 25-year-old Java HTTP Client API is old and has limitations. It was hard to use and hard to maintain. Part of the API supported protocols that aren’t used anymore, such as Gopher, which adds to complexity.

Plus, the old client is blocking and ties up a thread for the entire duration of a request, regardless of how long that is going to take. In addition, the old client was for the HTTP/1.1 protocol and needed to evolve to support the newer HTTP/2 protocol.

Fuchs: The new HTTP Client API started to incubate in JDK 9 and was later made standard and permanent in JDK 11. It supports both HTTP/1.1 and HTTP/2, supports WebSockets, and improves security. More importantly, the new client is modern and easy to use. For example, it uses the popular Builder pattern.

Delabassee: How does the new client work?

Fuchs: To send a request, you first need to create an HTTP client; there is a builder for that. Using a builder, you create a client instance and configure its state. For example, you can specify which protocol to use (such as HTTP/1.1 or HTTP/2) and whether to follow redirects. You also can set the proxy selector, set the necessary contexts for Transport Layer Security (TLS), and so on.

Once built, your HTTP client can be used to send multiple requests and is immutable. Requests can be sent multiple times, and you can choose whether they are sent synchronously or asynchronously.

Delabassée: Moving beyond the HTTP Client API, UNIX-domain socket channels were added to Java 16. What can you tell us about that?

McMahon: UNIX-domain sockets are like TCP/IP sockets, except that these sockets are used only for local interprocess communication (IPC), and they are addressed through file system pathnames, rather than through an IP address and port number.

UNIX-domain socket channels, which appeared in JEP 380, offer several benefits for applications that need to do only local IPC or IPC between containers. Performance is one benefit: By cutting out the TCP/IP stack, you can improve throughput and lower CPU utilization. These sockets can also improve security because if you need only local IPC, you don’t need to open ports to the network.

With UNIX-domain sockets, you can also make use of file system access controls and user credentials for additional security. When testing them with Docker, we found that UNIX-domain sockets make it easier to set up communication between containers.

To make all this work, JEP 380 added the following API elements:

◉ A new socket address class, java.net.UnixDomainSocketAddress

◉ A UNIX constant value in the existing java.net.StandardProtocolFamily enum

◉ New open factory methods on SocketChannel and ServerSocketChannel that specify the protocol family

◉ Updates to the SocketChannel and ServerSocketChannel specifications to allow you to define how the channels to the UNIX-domain sockets behave

It should also be mentioned that despite the name, UNIX-domain sockets are also available on Windows!

Delabassée: Typically, with a network connection, security is enforced at the network level, such as by using firewalls and reverse proxies. How can security be enforced with UNIX-domain sockets?

McMahon: One level of security is the old Java platform security model. For that, there’s a single permission, which enables or disables the feature if a security manager is running.

Beyond that, the main way you get security is through the operating system, its user and groups, and its file permissions, where you can restrict access to file system nodes through user IDs and group IDs.

An additional useful feature, which works on the UNIX platform but not on Windows, is via testing user credentials. If a client socket channel is connected to a server socket channel, either side can request the identity of the user at the opposite side.

Delabassée: There is a new simple web server and API targeted for Java 18. Can you give us a quick overview?

McMahon: JEP 408 will deliver a simple web server to serve static content. JEP 408 includes an API for programmatic creation and customization of the server and its components.

As its name implies, the Simple Web Server’s design is explicitly minimal. It is intended for development purposes, as well as for testing and educational use, but it is certainly not a full-featured server that you should use in production! For example, it doesn’t offer security features.

The Simple Web Server is based on the web server implementation in the com.sun.net.httpserver package that has been included in the JDK since 2006. That package is officially supported, and it’s extended with APIs to simplify server creation and enhance request handling. The Simple Web Server can be used via the dedicated command-line tool jwebserver or programmatically via its API.

To reiterate, the Simple Web Server is intended for educational, development testing, and debugging purposes only.

Delabassee: Any final thoughts about the new networking capabilities?

Fuchs: We discussed the HTTP Client API, UNIX-domain socket channels, and the Simple Web Server. Those features are quite visible because developers can use them directly. In parallel to those features, the Java teams continue to work on the platform itself, such as on some features that are not so visible but nevertheless are strategic going forward.

For example, slowly but surely Project Loom is coming—and so we are looking at making sure that our networking implementation will play nice with virtual threads. That was the motivation for JEP 353, which reimplements the legacy Socket API, and JEP 373, which reimplements the legacy DatagramSocket API.

Source: oracle.com

Monday, February 14, 2022

Natural language processing at your fingertips with OCI Language

Learn how to use cloud-based APIs to perform AI tasks, such as analyzing text for language.

One of several AI services released by Oracle for Oracle Cloud Infrastructure (OCI) is OCI Language, a cloud platform service that performs sophisticated text analysis at scale. It allows you to perform natural language processing on text, such as sentiment analysis, key-phrase extraction, text classification, named entity recognitions, and text classification.

OCI AI services offer prebuilt and easy-to-customize machine learning (ML) models. These AI services can help apply AI to applications and business operations. Developers don’t need to have any data science expertise to use them, and the prebuilt models don’t even require training data.

Oracle has announced several of these services (see Figure 1). Two—OCI Language and OCI Anomaly Detection—are generally available. OCI Speech, OCI Vision, and OCI Forecast are coming soon. (A list of all the announced services is at the end of this article.)

Oracle Java Exam Prep, Oracle Java Cert, Oracle Java Certification, Oracle Java Guides, Core Java
Figure 1. Five of the available OCI AI services

Running OCI Language from the console


Once you log in to OCI, you can find OCI Language by navigating to Analytics & AI > AI Services > Language. There you will be greeted by a getting-started page that describes many of the capabilities of OCI Language. If you click Analyze your text in the console, you will be able to try the capabilities, as shown in Figure 2.

Oracle Java Exam Prep, Oracle Java Cert, Oracle Java Certification, Oracle Java Guides, Core Java
Figure 2. Analyzing text with OCI Language

Click Analyze to see OCI Language in action, as shown in Figure 3.

Oracle Java Exam Prep, Oracle Java Cert, Oracle Java Certification, Oracle Java Guides, Core Java
Figure 3. Some of the capabilities of OCI Language

Calling OCI Language from a Java application


Calling OCI Language from a Java application is relatively simple, but you need to do some initial setup before calling the OCI Language API. This example will use OCI Language to detect the human language in some sample text.


I relied on Maven to get the libraries I needed, oci-java-sdk-ailanguage and oci-java-sdk-common. The following is what the dependencies section looked like in my pom.xml file:

<dependencies>
    <dependency>
        <groupId>com.oracle.oci.sdk</groupId>
        <artifactId>oci-java-sdk-ailanguage</artifactId>
        <version>2.12.0</version>
    </dependency>
    <dependency>
        <groupId>com.oracle.oci.sdk</groupId>
        <artifactId>oci-java-sdk-common</artifactId>
        <version>LATEST</version>
    </dependency>
</dependencies>

Authentication. For your local code to have the rights to use your subscribed-to OCI services, it needs to have the privileges to do so—either by having an instance principal or by acting on behalf of a user (by reading a configuration file with the user’s credentials).

For this example, I created a .oci/config file (as described in that article) to try the capability locally. See the code under Step 1 in Listing 1.

Listing 1. Calling the OCI Language API from Java

package com.company;
import com.oracle.bmc.ConfigFileReader;
import com.oracle.bmc.auth.AuthenticationDetailsProvider;
import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider;
import com.oracle.bmc.ailanguage.AIServiceLanguageClient;
import com.oracle.bmc.ailanguage.model.*;
import com.oracle.bmc.ailanguage.requests.*;
import com.oracle.bmc.ailanguage.responses.*;

import java.io.IOException;

public class Main {

    public static void main(String[] args) {
        try {
            /* Step 1: Just reading a config file with my credentials so the application acts on my behalf */
            final ConfigFileReader.ConfigFile configFile =
                    ConfigFileReader.parse("C:\\Users\\lecabrer\\.oci\\config", "DEFAULT");
            final AuthenticationDetailsProvider provider =
                    new ConfigFileAuthenticationDetailsProvider(configFile);

            /* Step 2: Create a service client */
            AIServiceLanguageClient client = new AIServiceLanguageClient(provider);

            /* Step 3: Create a request and dependent object(s). */
            DetectDominantLanguageDetails detectdominantLanguageDetails =
                    DetectDominantLanguageDetails.builder()
                    .text("Este es un texto en el idioma de mi madre, la mejor mamá del mundo.").build();

            DetectDominantLanguageRequest detectDominantLanguageRequest =
                    DetectDominantLanguageRequest.builder()
                    .detectDominantLanguageDetails(detectdominantLanguageDetails)
                    .opcRequestId("Just-some-unique-id")
                    .build();

            /* Step 4: Send request to the Client */
            DetectDominantLanguageResponse response = client.
                    detectDominantLanguage(detectDominantLanguageRequest);

            System.out.println("Detected language: " +
                            response.getDetectDominantLanguageResult().getLanguages().get(0).getName());
        }
        catch(IOException e) {
            e.printStackTrace();
        }
    }
}

Call the OCI Language API. You simply need to create a language client to issue the calls (Step 2 in Listing 1), prepare a request with its dependent object (Step 3 in Listing 1), and then send a request to the client (Step 4 in Listing 1).

And voila: The output is correct!

Detected language: Spanish

Conclusion


This article introduced you to OCI AI services and, more specifically, the capabilities of OCI Language. You saw the steps necessary to use the OCI Language SDK, with an end-to-end example.

This is a simple example, but there are many other prebuilt models in OCI Language that you can try. For instance, you may want to identify named entities, apply sentiment analysis to a set of records, or automatically classify text.

With AI services, you can leverage the power of AI without needing to have a background in data science or natural language processing.

Announced OCI AI services, as of February 2022


OCI Language. Performs text analysis at scale to understand unstructured text in documents, customer feedback interactions, support tickets, and social media. With built-in pretrained models, OCI Language eliminates the need for machine learning expertise and empowers developers to apply sentiment analysis, key-phrase extraction, text classification, named entity recognition, and more into their applications.

OCI Speech. Provides automatic speech recognition through prebuilt models trained on thousands of native and nonnative language speakers for real-time speech recognition. OCI Speech enables developers to easily convert file-based audio data containing human speech into highly accurate text transcriptions, and it can be used to provide in-workflow closed captions, index content, and enhance analytics on audio and video content.

OCI Vision. Provides pretrained computer vision models for image recognition and document analysis tasks. It also enables users to extend the models to other industry- and customer-specific use cases—such as scene monitoring, defect detection, and document processing—with their own data. OCI Vision can be used to detect visual anomalies in manufacturing, extract text from forms to automate business workflows, and tag items in images to count products or shipments.

OCI Anomaly Detection. Delivers business-specific anomaly detection models that flag critical irregularities early, which enables faster resolution and less operational disruption. OCI Anomaly Detection provides REST APIs and SDKs for several programming languages, which developers can use to easily integrate anomaly detection models into business applications. It is built on the patented MSET2 algorithm, which is used worldwide in highly sensitive situations such as nuclear reactor health monitoring, and it can be used for fraud detection, predicting equipment breakdown, and receiving data from multiple devices to predict failures.

OCI Forecasting. Delivers time-series forecasts through machine learning and statistical algorithms without the need for data science expertise. OCI Forecasting helps developers to quickly create accurate forecasts for their critical business metrics, including product demand, revenue, and resource requirements. These forecasts all have confidence intervals and explainability to help developers make the right business decisions.

OCI Data Labeling. Helps users build labeled datasets to train AI models. Users can assemble data, create, and browse data sets and also apply labels to data records through user interfaces and public APIs. The labeled data sets can be exported and used for model development across many of Oracle’s AI and data science services, including OCI Vision and OCI Data Science, for a consistent model-building experience.

Source: oracle.com

Friday, February 11, 2022

Design implications of Java’s switch statements and switch expressions

The two constructs might look similar, but they behave quite differently, and each is best suited for a different type of programmatic logic.

Download a PDF of this article

The recent evolution of the Java language has introduced interesting new features and constructs intended to make developers more productive. And it is one of these features, known as switch expressions, that I’d like to talk about.

I will start with the following assertion: switch statements and switch expressions have different design purposes. One is not just a different version of the other. Rather, switch expressions are a new construct intended for a different purpose than switch statements. To be clear, the older switch statement and the newer switch expression share some syntactical similarities, but they pursue significantly different design goals.

My intention in this article is not to just talk about this new construct from the perspective of its syntax but rather investigate its design implications for developers’ work. Surely, the intended developer productivity improvements are not meant to be measured in how fast you can type but rather in how well Java’s language constructs support coding requirements and help developers convey the meaning of the algorithm they are working on. This means that every Java developer needs to understand the intended purposes of such language constructs.

switch statements are not an alternative if/else construct

Let’s start by addressing one of the most common misconceptions about the switch statement, namely that it is usually explained by comparing it to the if/else statement as an alternative mechanism for implementing conditional logic.

In my opinion, this is not really the best way of understanding the switch statement. The if/else construct is designed to choose an alternate execution path, essentially presenting a binary fork mechanism that selects the way in which program logic would flow, as shown in Figure 1.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 1. The if/else construct chooses an alternate execution path.

However, the switch statement feels much more like a forward-only goto mechanism, as shown in Figure 2. It is not about a binary selection of an alternate execution path but rather a way of executing a jump into a block of code at a specified line labeled using a corresponding case. This means that switch statement logic is more like a goto operator than an if/else construct.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 2. The switch statement acts like a forward-only goto mechanism.

Once you understand this principle, the fall-through behavior of the switch statement appears fairly natural: When the switch statement evaluates a value that corresponds to a particular case statement, the execution path jumps directly to that case statement and execution continues from there.

After all, you certainly would expect an algorithm to continue progressing forward as usual after a goto action. As a matter of fact, this could even be exploited to your advantage.

Suppose you need to perform several actions that are not mutually exclusive, and yet not all the actions are always applicable. For example, imagine that you need to set a pricing discount based on product status, as follows (see Figure 3):

◉ For discontinued products, the discount should be increased by 0.2.
◉ For refurbished products, the discount should be increased by 0.1.
◉ For new products, the discount should be decreased by 0.1.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 3. Example of a "classic" switch statement

In addition to the above rules, there are two extra business constraints:

◉ Some discontinued products may also be refurbished, in which case both discounts should be applied.
◉ New products can’t be classified as refurbished or discontinued.

Notice that the first two cases (for discontinued and refurbished products) have an additive nature rather than a mutually exclusive nature. The use of the fall-through switch/case construct is perfectly suitable to reflect this type of business requirement, and the resulting code is more readable than an attempt to express the same logic with a complicated if/else construct. Of course, the same logic could be expressed using several if conditions, but it is obvious that the resulting code would be less straightforward and possibly harder to read, harder to test, and harder to maintain.

I believe in the great value of source code readability. Often, the same logic can be expressed in a program in many ways, so it may be worth considering readability as an important deciding factor when you code constructs. After all, code written in a convoluted and confusing fashion can be prone to errors and misinterpretations.

Selecting the best-matching semantical construct in a program is probably as important as in a human conversation: If you want to be understood, you should express your thoughts clearly, concisely, and unambiguously. (Similarly, coding is a conversation between you and the compiler—and also between you and whoever will be maintaining your code in the future.)

switch expression case statements don’t fall-through


Now let’s consider the design ideas behind a switch expression. The first thing to notice is that switch expression case statements are mutually exclusive and do not use the fall-through mechanism. That is because of the different purpose that switch expression design pursues, which is to derive and return a single value.

The expectation is that executing a switch expression yields a single value, and that case statements within the switch simply represent different ways of deriving that value, as shown in Figure 4.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 4. The switch expression uses case to represent different value derivations

The need to return a single value implies the mutually exclusive nature of these case statements; thus, switch expression case statements do not fall through and also they do not allow the use of a break statement.

Consider a business scenario where a product’s discount is dependent on a single, specific case. The main difference from the earlier example is that the discount for the discontinued and refurbished products is the same, but discount values are not added one after another, as shown in Figure 5. The -> arrow token indicates that you are using a switch expression rather than a switch statement.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 5. Example of using a switch expression to derive a value

Of course, this switch is now capable of returning a value, and it also needs to be terminated with a semicolon.

The style of the switch expression makes it somewhat like a SQL decode operator, treating the entire switch as a kind of value-derivation function.

There is one more consideration that is checked by a compiler, which is the need to guarantee that no matter what value is yielded from the expression, the list of case statements needs to be exhaustive. In other words, one case will always apply. This requires using a default case.

Another nice syntactical enchantment is the ability to create a comma-separated list of cases that should yield the same value. Clearly, this presents a good and nicely readable code construct, which perfectly reflects the intended purpose of conditionally deriving a value.

Interesting details about switch expressions


Technically speaking, returning a value from a switch expression is not a requirement, and it is also possible to construct more-complex switch expressions that create blocks of code to implement various cases. See Figure 6 and Figure 7.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 6. Example of using code blocks in a switch expression

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 7. Example of using the yield operator to return a value from the code block in a switch expression

Another significant difference between the older switch statement and the newer style of switch expression is that in switch expressions, each case statement has its own scope and may declare local variables independently from other case statements. In the older switch statement, case statements are treated as goto targets that are merely code labels within the same block, and thus all case statements share the same scope.

An additional operator, yield, was introduced in the Java language to help deal with the situation where you’d like to create a switch expression that returns a value and at the same time uses blocks of code to implement cases. In these circumstances, yield must be used to return a value for the relevant case.

As you might imagine, adding this new keyword to an established programming language is an inherently awkward thing to do because of its potential of breaking existing code that might have used the keyword yield as an identifier, for example, as a variable name.

To avoid such confusion, the new yield operator is not considered to be a keyword anywhere in Java except within the switch expression construct. When the switch expression specification was formulated, there was a discussion regarding the use of the break operator instead for this purpose. However, despite some backward compatibility inconveniences, it was decided that using yield to return a value is better than repurposing break, because using break would result in a much more confusing set of syntactical rules and produce less-readable and potentially misleading code.

Pattern matching for switch


Another interesting emerging development related to the switch construct is an introduction of the pattern matching capabilities for switch (see Figure 8).

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 8. Preview of pattern matching with a switch statement

Pattern matching is already a production feature for the instanceof operator. However, for switch it is still a preview feature and is described by JEP 406. Although it is not yet a production feature, this preview feature shows the direction in which switch is evolving.

The idea is to allow a switch statement or switch expression to accept an object and then trigger a specific case statement that matches this object type. Simultaneously, the object reference is cast to the appropriate type, and a locally scoped variable is introduced to the case statement to represent this casted object reference.

Please note that the actual syntax and implementation details for switch pattern matching can change in the future because this feature is not yet in production.

Source: oracle.com

Wednesday, February 9, 2022

Did You Know the Fastest Way of Serializing a Java Field is not Serializing it at All?

This article elaborates on different ways of serializing Java objects and benchmarks performance for the variants. Read this article and become aware of different ways to improve Java serialization performance.

In a previous article about open-source Chronicle Queue, there was some benchmarking and method profiling indicating that the speed of serialization had a significant impact on execution performance. After all, this is only to be expected as Chronicle Queue (and other persisted queue libraries) must convert Java objects located on the heap to binary data which is subsequently stored in files. Even for the most internally efficient libraries, this inevitable serialization procedure will largely dictate performance.

Data Transfer Object

In this article, we will use a Data Transfer Object (hereafter DTO) named  MarketData which contains financial information with a relatively large number of fields. The same principles apply to other DTOs in any other business area.

abstract class MarketData extends SelfDescribingMarshallable {

    long securityId;

    long time;

    // bid and ask quantities

    double bidQty0, bidQty1, bidQty2, bidQty3;

    double askQty0, askQty1, askQty2, askQty3;

    // bid and ask prices

    double bidPrice0, bidPrice1, bidPrice2, bidPrice3;

    double askPrice0, askPrice1, askPrice2, askPrice3;

    // Getters and setters not shown for clarity

}

Default Serialization


Java’s Serializable marker interface provides a default way to serialize Java objects to/from binary format, usually via the ObjectOutputStream and ObjectInputStream classes. The default way (whereby the magic writeObject() and readObject() are not explicitly declared) entails reflecting over an object’s non-transient fields and reading/writing them one by one, which can be a relatively costly operation.

Chronicle Queue can work with Serializable objects but also provides a similar, but faster and more space-efficient way to serialize data via the abstract class SelfDescribingMarshallable. Akin to Serializable objects, this relies on reflection but comes with substantially less overhead in terms of payload, CPU cycles, and garbage.

Default serialization often comprises the steps of:

◉ Identifying the non-transient fields using reflection
◉ Reading/writing the identified non-transient field values using reflection
◉ Writing/reading the field values to a target format (eg binary format)

The identification of non-transient fields can be cached, eliminating this step to improve performance.

Here is an example of a class using default serialization:

public final class DefaultMarketData extends MarketData {}

As can be seen, the class does not add anything over its base class and so it will use default serialization as transitively provided by SelfDescribingMarshallable.

Explicit Serialization


Classes implementing Serializable can elect to implement two magic private (sic!) methods whereby these methods will be invoked instead of resorting to default serialization.

This provides full control of the serialization process and allows fields to be read using custom code rather than via reflection which will improve performance. A drawback with this method is that if a field is added to the class, then the corresponding logic must be added in the two magic methods above or else the new field will not participate in serialization. Another problem is that private methods are invoked by external classes. This is a fundamental violation of encapsulation.

SelfDescribingMarshallable classes work in a similar fashion but thankfully it does not rely on magic methods and invoking private methods externally. A SelfDescribingMarshallable class provides two fundamentally different concepts of serializing: one via an intermediary Chronicle Wire open-source (which can be binary, text, YAML, JSON, etc) providing flexibility and one implicitly binary providing high performance. We will take a closer look at the latter one in the sections below.

Here is an example of a class using explicit serialization whereby public methods in implementing interfaces are explicitly declared:

public final class ExplicitMarketData extends MarketData {
 
    @Override
 
    public void readMarshallable(BytesIn bytes) {
 
        securityId = bytes.readLong();
 
        time = bytes.readLong();
 
        bidQty0 = bytes.readDouble();
 
        bidQty1 = bytes.readDouble();
 
        bidQty2 = bytes.readDouble();
 
        bidQty3 = bytes.readDouble();
 
        askQty0 = bytes.readDouble();
 
        askQty1 = bytes.readDouble();
 
        askQty2 = bytes.readDouble();
 
        askQty3 = bytes.readDouble();
 
        bidPrice0 = bytes.readDouble();
 
        bidPrice1 = bytes.readDouble();
 
        bidPrice2 = bytes.readDouble();
 
        bidPrice3 = bytes.readDouble();
 
        askPrice0 = bytes.readDouble();
 
        askPrice1 = bytes.readDouble();
 
        askPrice2 = bytes.readDouble();
 
        askPrice3 = bytes.readDouble();
 
    }
 
    @Override
 
    public void writeMarshallable(BytesOut bytes) {
 
        bytes.writeLong(securityId);
 
        bytes.writeLong(time);
 
        bytes.writeDouble(bidQty0);
 
        bytes.writeDouble(bidQty1);
 
        bytes.writeDouble(bidQty2);
 
        bytes.writeDouble(bidQty3);
 
        bytes.writeDouble(askQty0);
 
        bytes.writeDouble(askQty1);
 
        bytes.writeDouble(askQty2);
 
        bytes.writeDouble(askQty3);
 
        bytes.writeDouble(bidPrice0);
 
        bytes.writeDouble(bidPrice1);
 
        bytes.writeDouble(bidPrice2);
 
        bytes.writeDouble(bidPrice3);
 
        bytes.writeDouble(askPrice0);
 
        bytes.writeDouble(askPrice1);
 
        bytes.writeDouble(askPrice2);
 
        bytes.writeDouble(askPrice3);
 
    }
 
}

It can be concluded that this scheme relies on reading or writing each field explicitly and directly, eliminating the need to resort to slower reflection. Care must be taken to ensure fields are referenced in a consistent order and class fields must also be added to the methods above. 

Trivially Copyable Serialization


The concept of Trivially Copyable Java Objects is derived from and inspired by C++. 

As can be seen, the MarketData class above contains only primitive fields. In other words, there are no reference fields like String, List or the like. This means that when the JVM lays out the fields in memory, field values can be put adjacent to one another. The way fields are laid out is not specified in the Java standard which allows for individual JVM implementation optimizations. 

Many JVMs will sort primitive class fields in descending field size order and lay them out in succession. This has the advantage that read and write operations can be performed on even primitive type boundaries. Applying this scheme on the  ExplicitMarketData for example will result in the long time field being laid out first and, assuming we have the initial field space 64-bit aligned, allows the field to be accessed on an even 64-bit boundary. Next, the int securityId might be laid out, allowing it and all the other 32-bit fields to be accessed on an even 32-bit boundary. 

Imagine instead if an initial byte field were initially laid out, then subsequent larger fields would have to be accessed on uneven field boundaries. This would add a performance overhead for some operations, and would indeed prevent a small set of operations from being performed at all (eg unaligned CAS operations on the ARM architecture).

How is this relevant to high-performance serialization? Well, as it turns out, it is possible to access an object’s field memory region directly via Unsafe and use memcpy to directly copy the fields in one single sweep to memory or to a memory-mapped file. This effectively bypasses individual field access and replaces, in the example above, the many individual field accesses with a single bulk operation. 

The way this can be done in a correct, convenient, reasonably portable and safe way is outside the scope of this article. Luckily, this feature is readily available in Chronicle Queue, open-source Chronicle Bytes and other similar products out-of-the-box.

Here is an example of a class using trivially copyable serialization:

import static net.openhft.chronicle.bytes.BytesUtil.*;
 
public final class TriviallyCopyableMarketData extends MarketData {

    static final int START = 
 
            triviallyCopyableStart(TriviallyCopyableMarketData.class);
 
    static final int LENGTH = 
 
            triviallyCopyableLength(TriviallyCopyableMarketData.class);
 
    @Override
 
    public void readMarshallable(BytesIn bytes) {
 
        bytes.unsafeReadObject(this, START, LENGTH);
 
    }
 
    @Override
 
    public void writeMarshallable(BytesOut bytes) {
 
        bytes.unsafeWriteObject(this, START, LENGTH);
 
    }
 
}

This pattern lends itself well to scenarios where the DTO is reused. Fundamentally, It relies on invoking Unsafe under the covers for improved performance.

Benchmarks


Using JMH, serialization performance was assessed for the various serialization alternatives above using this class:

@State(Scope.Benchmark)
 
@BenchmarkMode(Mode.AverageTime)
 
@OutputTimeUnit(NANOSECONDS)
 
@Fork(value = 1, warmups = 1)
 
@Warmup(iterations = 5, time = 200, timeUnit = MILLISECONDS)
 
@Measurement(iterations = 5, time = 500, timeUnit = MILLISECONDS)
 
public class BenchmarkRunner {
 
    private final MarketData defaultMarketData = new DefaultMarketData();
 
    private final MarketData explicitMarketData = new ExplicitMarketData();
 
    private final MarketData triviallyCopyableMarketData = new TriviallyCopyableMarketData();
 
    private final Bytes<Void> toBytes = Bytes.allocateElasticDirect();
 
    private final Bytes<Void> fromBytesDefault = Bytes.allocateElasticDirect();
 
    private final Bytes<Void> fromBytesExplicit = Bytes.allocateElasticDirect();
 
    private final Bytes<Void> fromBytesTriviallyCopyable = Bytes.allocateElasticDirect();
 
    public BenchmarkRunner() {
 
        defaultMarketData.writeMarshallable(fromBytesDefault);
 
        explicitMarketData.writeMarshallable(fromBytesExplicit);
 
        triviallyCopyableMarketData.writeMarshallable(fromBytesTriviallyCopyable);
 
    }
 
    public static void main(String[] args) throws Exception {
 
        org.openjdk.jmh.Main.main(args);
 
    }
 
    @Benchmark
 
    public void defaultWrite() {
 
        toBytes.writePosition(0);
 
        defaultMarketData.writeMarshallable(toBytes);
 
    }
 
    @Benchmark
 
    public void defaultRead() {
 
        fromBytesDefault.readPosition(0);
 
        defaultMarketData.readMarshallable(fromBytesDefault);
 
    }
 
    @Benchmark
 
    public void explicitWrite() {
 
        toBytes.writePosition(0);
 
        explicitMarketData.writeMarshallable(toBytes);
 
    }
 
    @Benchmark
 
    public void explicitRead() {
 
        fromBytesExplicit.readPosition(0);
 
        explicitMarketData.readMarshallable(fromBytesExplicit);
 
    }
 
    @Benchmark
 
    public void trivialWrite() {
 
        toBytes.writePosition(0);
 
        triviallyCopyableMarketData.writeMarshallable(toBytes);
 
    }
 
    @Benchmark
 
    public void trivialRead() {
 
        fromBytesTriviallyCopyable.readPosition(0);
 
        triviallyCopyableMarketData.readMarshallable(fromBytesTriviallyCopyable);
 
    }
 
}

This produced the following output on a MacBook Pro (16-inch, 2019) with 2.3 GHz 8-Core Intel Core i9 CPU under JDK 1.8.0_312, OpenJDK 64-Bit Server VM, 25.312-b07:

Benchmark                      Mode  Cnt   Score   Error  Units
 
BenchmarkRunner.defaultRead    avgt    5  88.772 ± 1.766  ns/op
 
BenchmarkRunner.defaultWrite   avgt    5  90.679 ± 2.923  ns/op
 
BenchmarkRunner.explicitRead   avgt    5  32.419 ± 2.673  ns/op
 
BenchmarkRunner.explicitWrite  avgt    5  38.048 ± 0.778  ns/op
 
BenchmarkRunner.trivialRead    avgt    5   7.437 ± 0.339  ns/op
 
BenchmarkRunner.trivialWrite   avgt    5   7.911 ± 0.431  ns/op

Using the various MarketData variants, explicit serialization is more than two times faster than default serialization. Trivially copyable serialization is four times faster than explicit serialization and more than ten times faster than default serialization as illustrated in the graph below (lower is better):

Oracle Java Exam Prep, Oracle Java Preparation, Oracle Java Career, Oracle Java Skills, Oracle Java Guides, Core Java

More fields generally favour trivially copyable serialization over explicit serialization. Experience shows break-even is reached at around six fields in many cases. 

Interestingly, the concept of trivially copyable can be extended to hold data normally stored in reference fields such as a String or an array field. This will provide even more relative performance increase for such classes. Contact the Chronicle team if you want to learn more,

Why Does it Matter?


Serialization is a fundamental feature of externalizing DTOs to persistent queues, sending them over the wire or putting them in an off-heap Map and otherwise handling DTOs outside the Java heap. Such data-intensive applications will almost always gain performance and experience reduced latencies when the underlying serialization performance is improved.

Source: javacodegeeks.com

Friday, February 4, 2022

How the Java Language Could Better Support Composition and Delegation

Core Java, Java, Java Exam, Oracle Java Preparation, Oracle Java Guides, Oracle Java Skills, Oracle Java Certification

This article outlines a way of improving the Java language to better support composition and delegation. Engage in the discussion and contribute to evolving the Java Language.

The Java language lacks explicit semantic support for composition and delegation. This makes delegating classes hard to write, error-prone, hard to read and maintain. For example, delegating a JDBC ResultSet interface entails writing more than 190 delegating methods that essentially provide no additional information, as illustrated at the end of this article, and only add ceremony.

More generally, in the case of composition, Σ m(i) delegating methods need to be written where m(i) is the number of methods for delegate i (provided that all delegate method signatures are disjunct across all the delegates).  

The concept of language support for delegation is not new and there are numerous articles on the subject, including [Bettini08] and [Kabanov11]. Many other programming languages like Kotlin (“Derived”)  and Scala (“export”) have language support for delegation.

External Tools

Many IDEs have support for generating delegated methods. However, this neither impacts the ability to read nor understand a delegating class. Studies show that code is generally more read than written. There are third-party libraries that provide delegation (e.g. Lombok) but these are non-standard and provide a number of other drawbacks.

More generally, it would be possible to implement a subset of the functionality proposed here in third-party libraries leveraging annotation processors and/or dynamic proxies.

Trends and Industry Standards

As the drawbacks with inheritance were more deeply understood, the trend is to move towards composition instead. With the advent of the module system and generally stricter encapsulation policies, the need for semantic delegation support in the Java language has increased even more.

I think this is a feature that is best provided within the language itself and not via various third-party libraries. Delegation is a cornerstone of contemporary coding. 

In essence, It should be much easier to “Favor composition over inheritance” as stated in the book “Effective Java” by Joshua Bloch  [Bloch18, Item 18].

Java Record Classes

Many of the problems identified above were also true for data classes before record classes were introduced in Java 14. Upon more thorough analysis, there might be a substantial opportunity to harvest many of the findings made during the development of records and apply these in the field of delegation and composition.

On the Proposal

My intention with this article is not to present a concrete proposal of a way to introduce semantic support for composition and delegation in Java. On the contrary, if this proposal is one of the often 10-15 different discarded initial proposals and sketches on the path that needs to be traversed before a real feature can be proposed in the Java language, it will be a huge success. The way towards semantic support for composition and delegation in Java is likely paved with a number of research papers, several design proposals, incubation, etc. This feature will also compete against other features, potentially deemed to be more important to the Java ecosystem as a whole.

One motto for records was “model data as data” and I think that we should also “model delegation as delegation”.  But what is delegation? There are likely different views on this within the community. 

When I think of delegation, the following springs to mind: A delegating class has the following properties:

1. Has one or more delegates

2. Delegates methods from its delegates

3. Encapsulates its delegates completely

4. Implements and/or uses methods from its delegates (arguably)

An Outline – The Emissary

Core Java, Java, Java Exam, Oracle Java Preparation, Oracle Java Guides, Oracle Java Skills, Oracle Java Certification
In the following, I will present an outline to tackle the problem. In order to de-bikeshed the discussion, I will introduce a new keyword placeholder called “emissary” which is very unlikely ever to be used in a real implementation. This word could later be replaced by “delegator” or any other descriptive word suitable for the purpose or perhaps even an existing keyword.

An emissary class has many similarities to a record class and can be used as shown in the example below:

public emissary Bazz(Foo foo, Bar bar);

As can be seen, the Bazz class has two delegates (Foo and Bar) and consequently an equivalent desugared class  is created having two private final fields:

private final Foo foo;

private final Bar bar;

An emissary class is also provided with a constructor. This process could be the same as for records with canonical and compact constructors:

public final class Bazz {

    private final Foo foo;

    private final Bar bar;

    public Bazz(Foo foo, Bar bar) {

       this.foo = foo;

       this.bar = bar;

    }

}

It also makes the emissary class implement Foo and Bar. Because of this, Foo and Bar must be interfaces and not abstract or concrete classes. (In a variant of the current proposal, the implementing interfaces could be explicitly declared).

public final class Bazz implements Foo, Bar {

    private final Foo foo;

    private final Bar bar;

   public Bazz(Foo foo, Bar bar) {

       this.foo = foo;

       this.bar = bar;

   }

}

Now, in order to continue the discussion, we need to describe the example classes Foo and Bar a bit more which is done hereunder:

public interface Foo() {

    void f();

}

public interface Bar() {

    void b();

}

By declaring an emissary class we, unsurprisingly, also get the actual delegation methods so that Bazz will actually implement its interfaces Foo and Bar:

public final class Bazz implements Foo, Bar {

    private final Foo foo;

    private final Bar bar;

    public Bazz(Foo foo, Bar bar) {

        this. Foo = foo;

        this.bar = bar;

    }

    @Override

    void f() {

        foo.f();

    }

    @Override

    void b() {

        bar.b();

    }

}

If the delegates contain methods with the same signature, these would have to be explicitly “de-ambigued”, for example in the same way as default methods in interfaces. Hence, if Foo and Bar both implements c() then Bazz needs to explicitly declare c() to provide reconciliation. One example of this is shown here where both delegates are invoked:

@Override

void c() {

    foo.c();

    bar.c();

}

Nothing prevents us from adding additional methods by hand, for example, to implement additional interfaces the emissary class explicitly implements but that is not covered by any of the delegates.

It is also worth noting that the proposed emissary classes should not get a hashCode(), equals() or toString() methods generated. If they did, they would violate property C and leak information about its delegates. For the same reason, there should be no de-constructor for an emissary class as this bluntly would break encapsulation. Emissary classes should not implement Serializable and the likes by default.

An emissary class, just like a record class, is immutable (or at least unmodifiable and therefore shallowly immutable) and is hence thread-safe if all the delegates are.

Finally, an emissary class would extend java.lang.Emissary, a new proposed abstract class similar to java.lang.Enum and java.lang.Record.

Comparing Record with Emissary

Comparing the existing record and the proposed emissary classes yield some interesting facts:

record

◉ Provides a generated hashCode() method

◉ Provides a generated equals() method

◉ Provides a generated toString() method

◉ Provides component getters

◉ Cannot declare instance fields other than the private final fields which correspond to components of the state description

emissary

◉ Does not provide a generated hashCode() method

◉ Does not provide a generated equals() method

◉ Does not provide a generated  toString() method

◉ Provides delegating methods

◉ Implements delegates (in one variant)

◉ Can declare additional final instance fields other than the private final fields which correspond to delegates

both 

◉ A private final field for each component/delegate of the state description

◉ A public constructor, whose signature is the same as the state/delegate description, that initializes each field from the corresponding argument; (canonical constructor and compact constructor)

◉ Gives up the ability to decouple API from representation

◉ Implicitly final, and cannot be abstract (ensuring immutability)

◉ Cannot extend any other class (ensures immutability)

◉ Extends a java.lang class other than Object.

◉ Can declare additional methods not covered by the properties/delegates

Anticipated Use Cases

Here are some use cases of the emissary class:

Composition

Providing an Implementation for one or several interfaces using composition:

  public emissary FooAndBar(Foo foo, Bar bar);

Encapsulation

Encapsulating an existing instance of a class, hiding the details of the actual implementation:

private emissary EncapsulatedResultSet(ResultSet resultSet);

  …

  ResultSet rs = stmt.executeQuery(query);

  return new EncapsulatedResultSet(rs);

Disallow down-casting

Disallow the down-casting of an instance. I.e. an emissary class implements a restricted sub-set of its delegate’s methods where the non-exposed methods cannot be invoked via casting or reflection. 

String implements CharSequence and in the example below, we provide a String viewed as a CharSequence whereby we cannot down-cast the CharSequence wrapper back to a String. 

private emissary AsCharSequence(CharSequence s);

  return new AsCharSequence(“I am a bit incognito.”);

Services and Components

Providing an implementation of an interface that has an internal implementation. The internal component package is typically not exported in the module-info file:

public emissary MyComponent(MyComponent comp) {

      public MyComponent() {

          this(new InternalMyComponentImpl());

      }

      // Optionally, we may want to hide the public 

      // constructor

      private MyComponent(MyComponent comp) {

         this.comp = comp;

      } 

  }

  MyComponent myComp = ServiceLoader.load(MyComponent.class)

                           .iterator()

                           .next();

Note: If InternalMyComponentImpl is composed of an internal base class, contains annotations, has non-public methods, has fields etc. These will be completely hidden from direct discovery via reflection by the emissary class and under JPMS, it will be completely protected from deep reflection. 

Comparing Two ResultSet Delegators

Comparison between two classes delegating a ResultSet:

Emissary Class

// Using an emissary class. A one-liner

public emissary EncapsulatedResultSet(ResultSet resultSet);

IDE Generation

// Using automatic IDE delegation. About 1,000 lines!
 
public final class EncapsulatedResultSet implements ResultSet {
 
    private final ResultSet delegate;
 
 
    public EncapsulatedResultSet(ResultSet delegate) {
 
        this.delegate = delegate;
 
    }
 
    @Override
 
    public boolean next() throws SQLException {
 
        return delegate.next();
 
    }
 
  // About 1000 additional lines are not shown here for brevity…

Source: javacodegeeks.com

Wednesday, February 2, 2022

Java: How Object Reuse Can Reduce Latency and Improve Performance

Become familiar with the art of object reuse by reading this article and learn the pros and cons of different reuse strategies in a multi-threaded Java application. This allows you to write more performant code with less latency.

Core Java, Oracle Java Exam Prep, Oracle Java Career, Oracle Java Guides, Oracle Java Skills, Oracle Java Jobs, Oracle Java Learning

While the use of objects in object-oriented languages such as Java provides an excellent way of abstracting away complexity, frequent object creation can come with downsides in terms of increased memory pressure and garbage collection which will have an adverse effect on applications’ latency and performance. 

Carefully reusing objects provides a way to maintain performance while keeping most parts of the intended level of abstraction. This article explores several ways to reuse objects.

The Problem

By default, the JVM will allocate new objects on the heap. This means these new objects will accumulate on the heap and the space occupied will eventually have to be reclaimed once the objects go out of scope (i.e. are not referenced anymore) in a process called “Garbage Collection” or GC for short. As several cycles with creating and removing objects are passed, memory often gets increasingly fragmented.

While this works fine for applications with little or no performance requirements, it becomes a significant bottleneck in performance-sensitive applications. To make things worse, these problems are often exacerbated in server environments with many CPU cores and across NUMA regions.

Memory Access Latencies

Accessing data from main memory is relatively slow (around 100 cycles, so about 30 ns on current hardware compared to sub ns access using registers) especially if a memory region has not been accessed for long (leading to an increased probability for a TLB miss or even a page fault). Progressing towards more localized data residing in L3, L2, L1 CPU caches down to the actual CPU registers themselves, latency improves by orders of magnitude. Hence, it becomes imperative to keep a small working set of data.

Consequences of Memory Latencies and Dispersed Data

As new objects are created on the heap, the CPUs have to write these objects in memory locations inevitably located farther and farther apart as memory located close to the initial object becomes allocated. This might not be a far-reaching problem during object creation as cache and TLB pollution will be spread out over time and create a statistically reasonably evenly distributed performance reduction in the application. 

However, once these objects are to be reclaimed, there is a memory access “storm” created by the GC that is accessing large spaces of unrelated memory over a short period of time. This effectively invalidates CPU caches and saturates memory bandwidth which results in significant and non-deterministic application performance drops.

To make things worse, if the application mutates memory in a way that the GC cannot complete in a reasonable time, some GCs will intervene and stop all application threads so it can complete its task. This creates massive application delays, potentially in the seconds or even worse. This is referred to as “stop-the-world collections”.

Improved GCs

In recent years, there has been a significant improvement in GC algorithms that can mitigate some of the problems described above. However, fundamental memory access bandwidth limitations and CPU cache depletion problems still remain a factor when creating massive amounts of new objects.

Reusing Objects is Not Easy

Having read about the issues above, it might appear that reusing objects is a low-hanging fruit that can be easily picked at will. As it turns out, this is not the case as there are several restrictions imposed on object reuse.

An object that is immutable can always be reused and handed between threads, this is because its fields are final and set by the constructor which ensures complete visibility. So, reusing immutable objects is straightforward and almost always desirable, but immutable patterns can lead to a high degree of object creation.

However, once a mutable instance is constructed, Java’s memory model mandates that normal read and write semantics are to be applied when reading and writing normal instance fields (i.e. a field that is not volatile). Hence, these changes are only guaranteed to be visible to the same thread writing the fields. 

Hence, contrary to many beliefs, creating a POJO, setting some values in one thread and handing that POJO off to another thread will simply not work. The receiving thread might see no updates, might see partial updates (such as the lower four bits of a long were updated but not the upper ones) or all updates. To make thighs worse, the changes might be seen 100 nanoseconds later, one second later or they might never be seen at all. There is simply no way to know. 

Various Solutions

One way to avoid the POJO problem is to declare primitive fields (such as int and long fields) volatile and use atomic variants for reference fields. Declaring an array as volatile means only the reference itself is volatile and does not provide volatile semantics to the elements. This can be solved but the general solution is outside the scope of this article although the Atomic*Array classes provide a good start. Declaring all fields volatile and using concurrent wrapper classes may incur some performance penalty.

Another way to reuse objects is by means of ThreadLocal variables which will provide distinct and time-invariant instances for each thread. This means normal performant memory semantics can be used. Additionally, because a thread only executes code sequentially, it is also possible to reuse the same object in unrelated methods. Suppose a StringBuilder is needed as a scratch variable in a number of methods (and then reset the length of the StringBuilder back to zero between each usage), then a ThreadLocal holding the very same instance for a particular thread can be reused in these unrelated methods (provided no method calls a method that shares the reuse, including the method itself). Unfortunately, the mechanism around acquiring the ThreadLocal’s inner instance creates some overhead. There are a number of other culprits associated with the use of code-shared ThreadLocal variables making them:

◉ Difficult to clean up after use.

◉ Susceptible to memory leaks.

◉ Potentially unscalable. Especially because Java’s upcoming virtual thread feature promotes creating a massive amount of threads.

◉ Effectively constituting a global variable for the thread.

Also, it can be mentioned that a thread context can be used to hold reusable objects and resources. This usually means that the thread context will somehow be exposed in the API but the upshot is that it provides fast access to thread reused objects.  Because objects are directly accessible in the thread context, it provides a more straightforward and deterministic way of releasing resources. For example, when the thread context is closed. 

Lastly, the concept of ThreadLocal and thread context can be mixed providing an untainted API while providing simplified resource cleaning thereby avoiding memory leaks.

It should be noted that there are other ways to ensure memory consistency. For example, using the perhaps less known Java class Exchanger. The latter allows the exchange of messages whereby it is guaranteed that all memory operations made by the from-thread prior to the exchange happen-before any memory operation in the to-thread.

Yet another way is to use open-source Chronicle Queue which provides an efficient, thread-safe, object creation-free means of exchanging messages between threads. 

In Chronicle Queue, messages are also persisted, making it possible to replay messages from a certain point (e.g. from the beginning of the queue) and to reconstruct the state of a service (here, a thread together with its state is referred to as a service). If an error is detected in a service, then that error state can be re-created (for example in debug mode) simply by replaying all the messages in the input queue(s). This is also very useful for testing whereby a number of pre-crafted queues can be used as test input to a service.

Higher order functionality can be obtained by composing a number of simpler services, each communicating via one or more Chronicle Queues and producing an output result, also in the form of a Chronicle Queue. 

The sum of this provides a completely deterministic and decoupled event-driven microservice solution.

Reusing Objects in Chronicle Queue

Open-source Chronicle Queue was benchmarked and demonstrated to have high performance. One objective of this article is to take a closer look at how this is possible and how object reuse works under the hood in Chronicle Queue (using version 5.22ea6). 

As in the previous article, the same simple data object is used:

public class MarketData extends SelfDescribingMarshallable {

    int securityId;

    long time;

    float last;

    float high;

    float low;

    // Getters and setters not shown for brevity

}

The idea is to create a top-level object that is reused when appending a large number of messages to a queue and then analyse internal object usage for the entire stack when running this code:

public static void main(String[] args) {

    final MarketData marketData = new MarketData();

    final ChronicleQueue q = ChronicleQueue

            .single("market-data");

    final ExcerptAppender appender = q.acquireAppender();

    for (long i = 0; i < 1e9; i++) {

        try (final DocumentContext document =

                     appender.acquireWritingDocument(false)) {

             document

                    .wire()

                    .bytes()

                    .writeObject(MarketData.class, 

                            MarketDataUtil.recycle(marketData));

        }

    }

}

Since Chronicle Queue is serializing the objects to memory-mapped files, it is important that it does not create other unnecessary objects for the performance reasons stated above.

Memory Usage

The application is started with the VM option “-verbose:gc” so that any potential GCs are clearly detectable by observing the standard output. Once the application starts, a histogram of the most used objects are dumped after inserting an initial 100 million messages:

pemi@Pers-MBP-2 queue-demo % jmap -histo 8536

 num     #instances         #bytes  class name

----------------------------------------------

   1:         14901       75074248  [I

   2:         50548       26985352  [B

   3:         89174        8930408  [C

   4:         42355        1694200  java.util.HashMap$KeyIterator

   5:         56087        1346088  java.lang.String

2138:             1             16  sun.util.resources.LocaleData$LocaleDataResourceBundleControl

Total        472015      123487536

After the application appended about 100 million additional messages some seconds later, a new dump was made:

pemi@Pers-MBP-2 queue-demo % jmap -histo 8536

 num     #instances         #bytes  class name

----------------------------------------------

   1:         14901       75014872  [I

   2:         50548       26985352  [B

   3:         89558        8951288  [C

   4:         42355        1694200  java.util.HashMap$KeyIterator

   5:         56330        1351920  java.lang.String

2138:             1             16  sun.util.resources.LocaleData$LocaleDataResourceBundleControl

Total        473485      123487536

As can be seen, there was only a slight increase in the number of objects allocated (around 1500 objects) indicating no object allocation was made per message sent. No GC was reported by the JVM so no objects were collected during the sampling interval.

Designing such a relatively complex code path without creating any object while considering all the constraints above is of course non-trivial and indicates that the library has reached a certain level of maturity in terms of performance.

Profiling Methods

Profiling methods called during execution reveals Chronicle Queue is using ThreadLocal variables:

Core Java, Oracle Java Exam Prep, Oracle Java Career, Oracle Java Guides, Oracle Java Skills, Oracle Java Jobs, Oracle Java Learning

It spends about 7% of its time looking up thread-local variables via the

ThreadLocal$ThreadLocalMap.getEntry(ThreadLocal) method but this is well worth its effort compared to creating objects on the fly. 

As can be seen, Chronicle Queue spends most of its time accessing field values in the POJO to be written to the queue using Java reflection. Even though it is a good indicator that the intended action (i.e. copying values from a POJO to a Queue) appears somewhere near the top, there are ways to improve performance even more by providing hand-crafted methods for serialization substantially reducing execution time. But that is another story.

What’s Next?


In terms of performance, there are other features such as being able to isolate CPUs and lock Java threads to these isolated CPUs, substantially reducing application jitter as well as writing custom serializers.

Finally, there is an enterprise version with replication of queues across server clusters paving the way towards high availability and improved performance in distributed architectures. The enterprise version also includes a set of other features such as encryption, time zone rolling, and asynchronous message handling.

Source: javacodegeeks.com