java19 Archives - Piotr's TechBlog https://piotrminkowski.com/tag/java19/ Java, Spring, Kotlin, microservices, Kubernetes, containers Thu, 22 Dec 2022 11:15:38 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.1 https://i0.wp.com/piotrminkowski.com/wp-content/uploads/2020/08/cropped-me-2-tr-x-1.png?fit=32%2C32&ssl=1 java19 Archives - Piotr's TechBlog https://piotrminkowski.com/tag/java19/ 32 32 181738725 Java HTTP Server and Virtual Threads https://piotrminkowski.com/2022/12/22/java-http-server-and-virtual-threads/ https://piotrminkowski.com/2022/12/22/java-http-server-and-virtual-threads/#comments Thu, 22 Dec 2022 08:43:53 +0000 https://piotrminkowski.com/?p=13822 In this article, you will learn how to create an HTTP server with Java and use virtual threads for handling incoming requests. We will compare this solution with an HTTP server that uses a standard thread pool. Our test will compare memory usage in both scenarios under a heavy load of around 200 concurrent requests. […]

The post Java HTTP Server and Virtual Threads appeared first on Piotr's TechBlog.

]]>
In this article, you will learn how to create an HTTP server with Java and use virtual threads for handling incoming requests. We will compare this solution with an HTTP server that uses a standard thread pool. Our test will compare memory usage in both scenarios under a heavy load of around 200 concurrent requests.

If you like articles about Java you can also read my post about unknown and useful Java features. It is not my first article about virtual threads. I have already written about Java 19 virtual threads and support for them in the Quarkus framework in this article.

Source Code

If you would like to try it by yourself, you may always take a look at my source code. In order to do that you need to clone my GitHub repository. After that, you should follow my instructions.

Prerequisites

In order to do the exercise on your laptop you need to have JDK 19+ and Maven installed.

Enable Virtual Threads

Even if you have Java 19 that’s not all. Since virtual threads are still a preview feature in Java 19 we need to enable it during compilation. With Maven we need to enable preview features using maven-compiler-plugin as shown below.

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.10.1</version>
      <configuration>
        <release>19</release>
        <compilerArgs>
          --enable-preview
        </compilerArgs>
      </configuration>
    </plugin>
  </plugins>
</build>

Create HTTP Server with Virtual Threads

We don’t need much to create an HTTP or even HTTPS server with Java. In Java API, an object called HttpServer allows us to achieve it very easily. Once we will create the server we can override a default thread executor with the setExecutor method. No matter which type of executor we choose, there is one requirement that must be fulfilled by our server. It needs to be able to handle 200 requests simultaneously. Therefore for standard Java threads, we will create a pool with a maximum size of 200. For virtual threads, there is no sense to create any pools. They do not consume many resources since they are related directly to the OS.

Let’s take a look at the fragment of code visible below. That’s our method for creating an HTTP server. It will listen on 8080 port (1) under the /example context path (2). The SimpleDelayedHandler object handles all incoming requests. Depending on the value of the withLock variable, it will simulate delay without locking (false) or with ReentrantLock (true). In order to simplify the exercise, we can switch between standard (4) and virtual threads executor (3) using the single boolean parameter. After setting all required parameters, we can start the server (5).

private static void runServer(boolean virtual, boolean withLock) 
      throws IOException {
   
   HttpServer httpServer = HttpServer
         .create(new InetSocketAddress(8080), 0); // (1)

   httpServer.createContext("/example", 
      new SimpleDelayedHandler(withLock)); // (2)
   
   if (virtual) {
      httpServer.setExecutor(
            Executors.newVirtualThreadPerTaskExecutor()
      ); // (3)
   } else {
      httpServer.setExecutor(
            Executors.newFixedThreadPool(200)
      ); // (4)
   }

   httpServer.start(); // (5)
}

Then, we need to call the runServer method from the main method. We will test 4 scenarios depending on the value of two input arguments. We will discuss it in the next section.

public static void main(String[] args) throws IOException {
   runServer(true, false);
}

After running the server you can make a test call using the following command:

$ curl http://localhost:8080/example

Build Test Scenarios

As mentioned before, we will run four test scenarios. In the first two of them, we just compare the performance of the HTTP server with the standard thread pool and with virtual threads. We will simulate the processing time with the Thread.sleep method. In the next two scenarios, we will simulate the usage of the workers’ pool (1). For example, it can be something similar to using a JDBC connection pool in the REST app. There are 50 workers handling 200 requests (2). Those workers will also delay the thread execution with the Thread.sleep method, but this time they will lock the thread at the beginning of execution and unlock it at the end.

Depending on the value of the withLock input argument we will use the workers’ pool (3) or we will just sleep the thread (4). In both cases, we will finally return the response Ping_ and incremented number (5) represented by the AtomicLong object. Here’s the implementation of our handler.

public class SimpleDelayedHandler implements HttpHandler {

   private final List<SimpleWork> workers = 
      new ArrayList<>(); // (1)
   private final int workersCount = 50;
   private final boolean withLock;
   AtomicLong id = new AtomicLong();

   public SimpleDelayedHandler(boolean withLock) {
      this.withLock = withLock;
      if (withLock) {
         for (int i = 0; i < workersCount; i++) { // (2)
            workers.add(new SimpleWork());
         }
      }
   }

   @Override
   public void handle(HttpExchange t) throws IOException {
      String response = null;
      if (withLock) {
         response = workers
            .get((int) (id.incrementAndGet() % workersCount))
            .doJob();
      } else {
         try {
            Thread.sleep(200);
         } catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
         response = "Ping_" + id.incrementAndGet();
      }

      t.sendResponseHeaders(200, response.length());
      OutputStream os = t.getResponseBody();
      os.write(response.getBytes());
      os.close();
   }
}

Here’s the implementation of our worker. As you it also sleeps the thread (this time for 100 milliseconds). However, during that time it locks the object. Since we have 50 worker objects in the pool only 50 threads may use it at the same time. Others will wait until the lock will be released.

public class SimpleWork {

   AtomicLong id = new AtomicLong();
   ReentrantLock lock = new ReentrantLock();

   public String doJob() {
      String response = null;
      lock.lock();
      try {
         Thread.sleep(100);
         response = "Ping_" + id.incrementAndGet();
      } catch (InterruptedException e) {
         throw new RuntimeException();
      } finally {
         lock.unlock();
      }
      return response;
   }

}

Load Test for Java Virtual vs Standard Threads

Let’s begin with the first scenario. We will test standard threads without any locking workers simulation.

public static void main(String[] args) throws IOException {
   runServer(false, false);
}

We can make some warmup tests as shown below. I’m using the siege tool for load testing. We can define the number of concurrent threads and the number of repetitions.

In the right test, we will simulate 200 concurrent requests.

$  siege http://localhost:8080/example -c 200 -r 500

Let’s switch to the profiler view. Here you can see heap memory usage during the test. The usage is around 300 MB, while the reservation is more than 500 MB.

java-virtual-threads-memory-standard

Let’s take a look at the telemetry view. As you see there are ~200 running threads.

Now, we will run the same test for the HTTP server using virtual threads. Let’s restart the application with the following arguments:

public static void main(String[] args) throws IOException {
   runServer(true, false);
}

Let’s switch to the profiler view once again. Here you can see heap memory usage during the test. You can compare it to the previous results. Now the usage is around 180 MB, while the reservation is around 300 MB.

java-virtual-threads-memory-virtual

Here’s the telemetry view. There are just some (~10) platform threads that “carry” virtual threads.

Here’s the visualization of the thread pool from the beginning of the test. As you see there are just some platform threads (CarrierThreads) and a lot of short-lived virtual threads.

java-virtual-threads-pool

Locks with Virtual Threads

In the end, let’s make the same checks, but this time with our worker objects pool that uses ReentrantLock to synchronize threads. Firstly, we will start the app with the following arguments to test standard threads.

public static void main(String[] args) throws IOException {
   runServer(false, true);
}

In fact, for standard threads, the main difference is in thread pool visualization. As you see, now many threads waiting for the lock to release. Our workers’ pool became a bottleneck for the app.

java-virtual-threads-histogram

It doesn’t have any impact on RAM usage in comparison to the previous test for standard Java threads.

And finally the last scenario. Now, we will do the same check for virtual threads.

public static void main(String[] args) throws IOException {
   runServer(true, true);
}

Here are the results for memory usage.

In thread pool visualization we have just some “carrier” threads. As you see they are not “locked”.

In the “Thread Monitor” view there are a lot of virtual threads that wait a moment until the lock is released.

java-virtual-threads-virtual-locks

Of course, you can clone my GitHub repo and make your own tests. I was using JProfiler for memory and threads visualization.

Final Thoughts

Java virtual threads are really long-awaited feature. Since they are still in the preview status in Java 19 we need to wait for their wide adoption in the most popular Java libraries. Unfortunately, even Java 19 is not an LTS and if you are working for one of those companies that only use LTS versions you will have to wait for Java 21 which should be released in September 2023. Nevertheless, virtual threads can reduce the effort of writing, maintaining, and observing especially for high-throughput concurrent applications. We can use them as simply as the standard Java threads. The aim of this article was to show you how you can start with virtual threads to build your own solution, for example, an HTTP server. Then you can easily compare the difference in performance between standard and virtual threads.

The post Java HTTP Server and Virtual Threads appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2022/12/22/java-http-server-and-virtual-threads/feed/ 8 13822
Quarkus with Java Virtual Threads https://piotrminkowski.com/2022/10/06/quarkus-with-java-virtual-threads/ https://piotrminkowski.com/2022/10/06/quarkus-with-java-virtual-threads/#comments Thu, 06 Oct 2022 08:38:10 +0000 https://piotrminkowski.com/?p=13542 In this article, you will learn how to integrate Quarkus with Java virtual threads. Currently, virtual threads is one the hottest topics in Java world. It has been introduced in Java 19 as a preview feature. Virtual threads reduce the effort of writing, maintaining, and observing high-throughput concurrent applications. In fact, it is one of […]

The post Quarkus with Java Virtual Threads appeared first on Piotr's TechBlog.

]]>
In this article, you will learn how to integrate Quarkus with Java virtual threads. Currently, virtual threads is one the hottest topics in Java world. It has been introduced in Java 19 as a preview feature. Virtual threads reduce the effort of writing, maintaining, and observing high-throughput concurrent applications. In fact, it is one of the biggest changes that comes to Java in the last few years. Do you want to try how it works? It seems that the Quarkus framework provides an easy way to start with virtual threads. Let’s begin.

There are a lot of articles about Quarkus on my blog. If you don’t have any experience with that framework and you are looking for something to begin you can read, for example, the following article. Also, there are a lot of simple guides to the Quarkus features available on its website here.

Source Code

If you would like to try this exercise yourself, you may always take a look at my source code. In order to do that, you need to clone my GitHub repository. Then you need to go to the person-virtual-service directory. After that, just follow my instructions.

Prerequisites

Before we start, we need to install several tools on the local machine. First of all, you have to install JDK 19. There are also some other tools I’m using in that exercise:

  • Docker – our sample Quarkus app connects to the Postgresql database. We can easily run Postgres on Docker automatically using the Quarkus Dev Services feature
  • JProfiler – in order to visualize and observe the thread pool
  • k6 – a javascript-based tool for load testing

Use Case

We will build a simple REST application using the Quarkus framework. It will connect to the Postgres database and expose some endpoints for adding data and the basic search operations. Apart from enabling virtual threads support in Quarkus, our main goal is not to block the thread. In the virtual threads nomenclature, this thread is called “carrier thread”. It is the platform thread responsible for executing a virtual thread. It might be blocked by e.g. JDBC client driver. In order to avoid it, we should use non-blocking clients.

Dependencies

We need to include two Quarkus modules into the Maven dependencies. The first of them is Quarkus Resteasy Reactive which provides an implementation of JAX-RS specification and allows us to create reactive REST services. The quarkus-reactive-pg-client module provides an implementation of a reactive driver for the Postgres database.

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>

We should also set JDK 19 as a default compiler. Since virtual threads are available as a preview feature we need to set --enable-preview as a JVM argument.

<plugin>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-maven-plugin</artifactId>
  <version>${quarkus.version}</version>
  <extensions>true</extensions>
  <executions>
    <execution>
      <goals>
        <goal>build</goal>
        <goal>generate-code</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <source>19</source>
    <target>19</target>
    <compilerArgs>
      <arg>--enable-preview</arg>
    </compilerArgs>
    <jvmArgs>--enable-preview --add-opens java.base/java.lang=ALL-UNNAMED</jvmArgs>
  </configuration>
</plugin>

I’m also generating some test data for load tests. There is a really useful library for that – Datafaker.

<dependency>
  <groupId>net.datafaker</groupId>
  <artifactId>datafaker</artifactId>
  <version>1.6.0</version>
</dependency>

Enable Virtual Threads for Reactive Services

The Quarkus Resteasy Reactive module works in a non-blocking, event-loop style way. Instead of using the “event loop”, we need to force the execution of an endpoint handler on a new virtual thread. To do that we need to annotate the REST endpoint with the @RunOnVirtualThread. The rest of the code may be pretty similar to the blocking-style way of building services. Of course, instead of blocking the database driver, we will use its reactive alternative. The implementation is provided inside the PersonRepositoryAsyncAwait class.

@Path("/persons")
public class PersonResource {

   @Inject
   PersonRepositoryAsyncAwait personRepository;
   @Inject
   Logger log;

   @POST
   @RunOnVirtualThread
   public Person addPerson(Person person) {
      person = personRepository.save(person);
      return person;
   }

   @GET
   @RunOnVirtualThread
   public List<Person> getPersons() {
      return personRepository.findAll();
   }

   @GET
   @Path("/name/{name}")
   @RunOnVirtualThread
   public List<Person> getPersonsByName(@PathParam("name") String name) {
      return personRepository.findByName(name);
   }

   @GET
   @Path("/age-greater-than/{age}")
   @RunOnVirtualThread
   public List<Person> getPersonsByName(@PathParam("age") int age) {
      return personRepository.findByAgeGreaterThan(age);
   }

   @GET
   @Path("/{id}")
   @RunOnVirtualThread
   public Person getPersonById(@PathParam("id") Long id) {
      log.infof("(%s) getPersonById(%d)", Thread.currentThread(), id);
      return personRepository.findById(id);
   }

}

Let’s switch to the repository implementation. We use an implementation of the SmallRye Mutiny client for Postgres. The PgPool client allows us to create and execute SQL queries in a non-blocking way. In order to create a query, we should call the preparedQuery method. Finally, we need to invoke the executeAndAwait method to perform the operation asynchronously.

@ApplicationScoped
public class PersonRepositoryAsyncAwait {

   @Inject
   PgPool pgPool;
   @Inject
   Logger log;

   public Person save(Person person) {
      Long id = pgPool
         .preparedQuery("INSERT INTO person(name, age, gender) VALUES ($1, $2, $3) RETURNING id")
         .executeAndAwait()
         .iterator().next().getLong("id");
      person.setId(id);
      return person;
   }

   public List<Person> findAll() {
      log.info("FindAll()" + Thread.currentThread());
      RowSet<Row> rowSet = pgPool
         .preparedQuery("SELECT id, name, age, gender FROM person")
         .executeAndAwait();
      return iterateAndCreate(rowSet);
   }

   public Person findById(Long id) {
      RowSet<Row> rowSet = pgPool
         .preparedQuery("SELECT id, name, age, gender FROM person WHERE id = $1")
         .executeAndAwait(Tuple.of(id));
      List<Person> persons = iterateAndCreate(rowSet);
      return persons.size() == 0 ? null : persons.get(0);
   }

   public List<Person> findByName(String name) {
      RowSet<Row> rowSet = pgPool
         .preparedQuery("SELECT id, name, age, gender FROM person WHERE id = $1")
         .executeAndAwait(Tuple.of(name));
      return iterateAndCreate(rowSet);
   }

   public List<Person> findByAgeGreaterThan(int age) {
      RowSet<Row> rowSet = pgPool
         .preparedQuery("SELECT id, name, age, gender FROM person WHERE age > $1")
         .executeAndAwait(Tuple.of(age));
      return iterateAndCreate(rowSet);
   }

   private List<Person> iterateAndCreate(RowSet<Row> rowSet) {
      List<Person> persons = new ArrayList<>();
      for (Row row : rowSet) {
         persons.add(Person.from(row));
      }
      return persons;
   }

}

Prepare Test Data

Before we run load tests let’s add some test data to the Postgres database. We will use the Datafaker library for generating persons’ names. We will use the same reactive, non-blocking PgPool client as before. The following part of the code generates and inserts 1000 persons into the database on the Quarkus app startup. It is a part of our repository implementation.

@ApplicationScoped
public class PersonRepositoryAsyncAwait {

   // ... methods

   @Inject
   @ConfigProperty(name = "myapp.schema.create", defaultValue = "true")
   boolean schemaCreate;

   void config(@Observes StartupEvent ev) {
      if (schemaCreate) {
         initDb();
      }
   }

   private void initDb() {
      List<Tuple> persons = new ArrayList<>(1000);
      Faker faker = new Faker();
      for (int i = 0; i < 1000; i++) {
         String name = faker.name().fullName();
         String gender = faker.gender().binaryTypes().toUpperCase();
         int age = faker.number().numberBetween(18, 65);
         int externalId = faker.number().numberBetween(100000, 999999);
         persons.add(Tuple.of(name, age, gender, externalId));
      }

      pgPool.query("DROP TABLE IF EXISTS person").execute()
         .flatMap(r -> pgPool.query("""
                  create table person (
                    id serial primary key,
                    name varchar(255),
                    gender varchar(255),
                    age int,
                    external_id int
                  )
                  """).execute())
         .flatMap(r -> pgPool
            .preparedQuery("insert into person(name, age, gender, external_id) values($1, $2, $3, $4)")
            .executeBatch(persons))
         .await().indefinitely();
    }
}

Load Testing with k6 and JProfiler

I’m using the k6 tool for load testing, but you can use any other popular tool as well. In order to install it on macOS just run the following homebrew command:

$ brew install k6

The k6 tool uses Javascript for creating tests. We need to prepare an input with the test definition. My test generates a number between 1 and 1000 and then places it as a path parameter for the GET /persons/{id} endpoint. Finally, it checks if the response status is 200 and the body is not empty.

import http from 'k6/http';
import { sleep, check } from 'k6';

export default function () {
  let r = Math.floor(Math.random() * 1000) + 1;
  const res = http.get(`http://localhost:8080/persons/${r}`);
  check(res, {
    'is status 200': (res) => res.status === 200,
    'body size is > 0': (r) => r.body.length > 0,
  });
  sleep(1);
}

To simplify running the Quarkus app from your IDE you can annotate the main class with @QuarkusMain.

@QuarkusMain
public class PersonVirtualApp {

   public static void main(String... args) {
      Quarkus.run(args);
   }

}

Don’t forget to enable Java preview mode in your IDE. Here’s how it looks in IntelliJ.

Once you run the app and then attach JProfiler to the running JVM process you may execute tests with k6. To do that pass the location of the file with the test definition, and then set the number of concurrent threads (--vus parameter). Finally set the test duration with the --duration parameter. I’m running the test four times using a different number of concurrent threads (20, 50, 100, 200).

$ k6 run --vus 20 --duration 90s k6-test.js

Analyze Test Results

Let’s take a look at the fragment of application logs. You can see that there are many virtual threads running by the same “carrier” thread, for example, on the ForkJoinPool-1-worker-9.

Let’s switch to the JProfiler UI for a moment. Here’s the fragment of a thread pool visualization.

quarkus-virtual-threads-pool-diagram

We run the same test four times with a different number of concurrent users (20, 50, 100, 200). Here’s a visualization of total Java threads (non-virtual) running. As you can see there is no significant difference between tests for 20 and 200 users.

quarkus-virtual-threads-total-threads

During the first test (20 virtual users), we generated ~1.8k requests in 90 seconds.

quarkus-virtual-threads-k6-test

During the last test (200 virtual users), we generated ~17.8k requests in 90 seconds.

Final Thoughts

One of the most important things I really like in Quarkus is that it is always up-to-date with the latest features. Once, the virtual threads have been released I can refactor my current app and use them instead of the standard platform threads. In this article, I presented how to use virtual threads with the app that connects to the database. If you are interested in other articles about Quarkus you can see the full list here. Enjoy 🙂

The post Quarkus with Java Virtual Threads appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2022/10/06/quarkus-with-java-virtual-threads/feed/ 6 13542