Java Next!

From Amber to Loom, from Panama to Valhalla

Developer Advocate

Java Team at Oracle

Lots to talk about!

Project Amber
Project Panama
Project Valhalla
Project Loom

Project Amber

Smaller, productivity-oriented Java language features

Profile:

Motivation

Some downsides of Java:

  • can be cumbersome

  • tends to require boilerplate

  • situational lack of expressiveness

Amber continuously improves that situation.

Delivered

Pattern Matching

Amber’s main thrust is pattern matching:

  • sealed types

  • records

  • type pattern matching

  • switch expressions

Inside Java Newscast #29

inside java newscast 29

String composition

Composing strings in Java is cumbersome:

String property = "last_name";
String value = "Doe";

// concatenation
String query =
	"SELECT * FROM Person p WHERE p."
		+ property + " = '" + value + "'";
// formatting
String query =
	"SELECT * FROM Person p WHERE p.%s = '%s'"
		.formatted(property, value);

Comes with free SQL injection! 😳

String interpolation

Why not?

// (fictional syntax!)
String query =
	"SELECT * FROM Person p "
		+ "WHERE p.\{property} = '\{value}'";

Also comes with free SQL injection! 😳

String interpolation

SQL injections aren’t the only concern.

These also need validation and sanitization:

  • HTML/XML

  • JSON

  • YAML

  • …​

All follow format-specific rules.

String templating

Enter String Templates (JEP 430):

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

Ingredients:

  • template with embedded expressions
    ~> TemplatedString

  • template processor (e.g. STR):
    transforms TemplatedString into String*

Template procesor STR

String form = STR."""
	Desc     Unit   Qty   Amount
	\{desc} $\{price} \{qty} $\{price * qty}

	Subtotal  $\{price * qty}
	Tax       $\{price * qty * tax}
	Total     $\{price * qty * (1.0 + tax)}
	""";
Desc     Unit   Qty   Amount
hammer   $7.88  3     $23.64

Subtotal  $23.64
Tax       $3.546
Total     $27.186

Template processor FMTR

String form = FMTR."""
	Desc        Unit      Qty   Amount
	%-10s\{desc} $%5.2f\{price} %5d\{qty} $%5.2f\{price * qty}

	Subtotal  $%5.2f\{price * qty}
	Tax       $%5.2f\{price * qty * tax}
	Total     $%5.2f\{price * qty * (1.0 + tax)}
	""";
Desc        Unit      Qty   Amount
hammer      $ 7.88      3   $23.64

Subtotal  $23.64
Tax       $ 3.55
Total     $27.19

Why strings?

Often, strings are just exchange format, e.g.:

  • start with: String + values

  • validate / sanitize to: String

  • parse to: Statement, JSONObject, …​

Why the detour?

Custom templating

The TemplateProcessor interface:

interface TemplateProcessor<RESULT, EXCEPTION> {
	RESULT apply(TemplatedString s) throws EXCEPTION;
}

RESULT can be of any type!

Custom templating

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

JSONObject doc = JSON."""
	{
		"name":    "\{name}",
		"phone":   "\{phone}",
		"address": "\{address}"
	};
	""";

Amber endeavors

Template strings: JEP 430

Pattern matching:

Other endeavors and conversations:

Project Amber

  • makes Java more expressive

  • reduces amount of code

  • makes us more productive

Timeline

My personal (!) guesses (!!):

JDK 20 (2023)
  • pattern matching in switch (4th preview; JEP 433)

  • record patterns (2nd preview; JEP 432)

JDK 21 (2023)
  • template strings preview (JEP 430)

  • first steps to simplified main

2024
  • more simplified main

  • primitive types in patterns

  • unnamed patterns

Deeper Dives

Project Panama

Interconnecting JVM and native code

Profile:

Subprojects

  • vector API

  • foreign memory API

  • foreign function API

Vectorization

Given two float arrays a and b,
compute c = - (a² + b²):

// a, b, c have same length
void compute(float[] a, float[] b, float[] c) {
	for (int i = 0; i < a.length; i++) {
		c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
	}
}

Auto-vectorization

Vectorization - modern CPUs:

  • have multi-word registers (e.g. 512 bit)

  • can store several numbers (e.g. 16 float​s)

  • can execute several computations at once

Single Instruction, multiple data (SIMD)

Just-in-time compiler tries to vectorize loops.
Auto-vectorization

Works but isn’t reliable.

Vector API

static final VectorSpecies<Float> VS =
	FloatVector.SPECIES_PREFERRED;

// a, b, c length is multiple of vector length
void compute(float[] a, float[] b, float[] c) {
	int upperBound = VS.loopBound(a.length);
	for (i = 0; i < upperBound; i += VS.length()) {
		var va = FloatVector.fromArray(VS, a, i);
		var vb = FloatVector.fromArray(VS, b, i);
		var vc = va.mul(va)
			.add(vb.mul(vb))
			.neg();
		vc.intoArray(c, i);
	}
}

Vector API

Properties:

  • clear and concise API (given the requirements)

  • platform agnostic

  • reliable run-time compilation and performance

  • graceful degradation

Foreign-memory API

Storing data off-heap is tough:

  • ByteBuffer is limited (2GB) and inefficient

  • Unsafe is…​ unsafe and not supported

Foreign-memory API

Panama introduces safe and performant API:

  • to allocate:
    MemorySegment, SegmentAllocator

  • to access/manipulate: MemoryLayout, VarHandle

  • control (de)allocation: Arena, SegmentScope

Foreign-memory API

// create `Arena` to manage off-heap memory lifetime
try (Arena offHeap = Arena.openConfined()) {
	// [allocate off-heap memory to store pointers]
	// [do something with off-heap data]
	// [copy data back to heap]
} // off-heap memory is deallocated here

Foreign-memory API

Allocate off-heap memory to store pointers:

String[] javaStrings = { "mouse", "cat", "dog" };
// Arena offHeap = ...

MemorySegment pointers = offHeap.allocateArray(
	ValueLayout.ADDRESS, javaStrings.length);
for (int i = 0; i < javaStrings.length; i++) {
	// allocate off-heap & store a pointer
	MemorySegment cString = offHeap
		.allocateUtf8String(javaStrings[i]);
	pointers
		.setAtIndex(ValueLayout.ADDRESS, i, cString);
}

Foreign-memory API

Copy data back to heap:

// String[] javaStrings = ...
// MemorySegment pointers =

for (int i = 0; i < javaStrings.length; i++) {
	MemorySegment cString = pointers
		.getAtIndex(ValueLayout.ADDRESS, i);
	javaStrings[i] = cString.getUtf8String(0);
}

Foreign-function API

JNI isn’t ideal:

  • involves several tedious artifacts (header file, impl, …​)

  • can only interoperate with languages that align
    with OS/architecture the JVM was built for

  • doesn’t reconcile Java/C type systems

Foreign-function API

Panama introduces streamlined tooling/API
based on method handles:

  • jextract: generates method handles from header file

  • classes to call foreign functions
    Linker, FunctionDescriptor, SymbolLookup

Foreign-function API

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

String[] javaStrings = { "mouse", "cat", "dog" };
try (Arena offHeap = Arena.openConfined()) {
	// [move Java strings off heap]
	// invoke foreign function
	radixSort.invoke(
		offHeap, javaStrings.length,
		MemoryAddress.NULL, '\0');
	// [copy data back to heap]
}

Project Panama

  • connects Java with the native world

  • offers safe, detailed, and performant APIs

Timeline

Official plans:

JDK 20 (2023)
  • foreign APIs (2nd preview; JEP 434)

Vector API needs to wait for Valhalla’s
primitive types and universal generics.

Deeper Dives

Deeper Dives

Project Valhalla

Advanced Java VM and Language feature candidates

Profile:

Motivation

Java has a split type system:

  • primitives

  • classes

We can only create classes, but:

  • have identity

  • are references

Identity

All classes come with identity:

  • extra memory for header

  • mutability

  • locking, synchronization, etc.

But not all custom types need that!

References

All classes come as references:

  • memory access indirection

  • nullability

  • protect from tearing

But not all custom types need that!

Project Valhalla

Valhalla’s goals is to unify the type system:

  • value types (disavow identity)

  • primitive types (disavow identity + references)

  • universal generics (ArrayList<int>)

  • specialized generics (backed by int[])

Value types

value class RationalNumber {
	private long nominator;
	private long denominator;

	// constructor, etc.
}

Codes (almost) like a class - exceptions:

  • class and fields are implcitly final

  • superclasses are limited

Value type behavior

No identity:

  • some runtime operations throw exceptions

  • "identity" check == compares by state

Benefits:

  • guaranteed immutability

  • more expressiveness

  • more optimizations

Value type behavior

Value types are references:

  • null is default value

  • no tearing

Migration to value types

The JDK (as well as other libraries) has many value-based classes, such as Optional and LocalDateTime. […​] We plan to migrate many value-based classes in the JDK to value classes.

Primitive types

primitive class ComplexNumber {
	private long rational;
	private long irrational;

	// constructor, etc.
}

Codes (almost) like a value class - exception:

  • no field of own type (i.e. no circularity)

Primitive type behavior

No identity (like value types).

No references:

  • default value has all fields set to their defaults

  • can tear under concurrent assignment

Benefit:

  • performance comparable to that of today’s primitives!

Primitive "boxes"

Sometimes, even int needs to be a reference:

  • nullability

  • non-tearability

  • self-reference

So we box to Integer.

What about ComplexNumber?

Primitive "boxes"

Each primitive class P declares two types:

  • P: as discussed so far

  • P.ref: behaves like a value type

primitive class Node<T> {
	T value;
	Node.ref<T> nextNode;
}

Migration to primitive types

[W]e want to adjust the basic primitives (int, double, etc.) to behave as consistently with new primitives as possible.

On the example of int/Integer:

  • declare int as primitive class

  • alias Integer with int.ref

  • remove Integer

Universal generics

When everybody creates their own values and primitives,
boxing becomes omni-present and very painful!

Universal generics allow value/primitive
classes as type parameters:

List<long> ids = new ArrayList<>();
List<RationalNumber> numbers = new ArrayList<>();

Specialized generics

Healing the rift in the type system is great!

But if ArrayList<int> is backed by Object[],
it will still be avoided in many cases.

Specialized generics will fix that:
Generics over primitives will avoid references!

Project Valhalla

Value and primitive types plus
universal and specialized generics:

  • fewer trade-offs between
    design and performance

  • no more manual specializations

  • better performance

  • can express design more clearly

  • more robust APIs

Makes Java more expressive and performant.

Timeline

My personal (!) guesses (!!):

2023
2024
2025
  • specialized generics preview

Deeper Dives

Ad break

There’s much more going on!

APIs

Lots of API additions and changes:

  • IP address resolution SPI ⑱ (JEP 418)

  • new random generator API ⑰ (JEP 356)

  • Unix domain sockets ⑯ (JEP 380)

  • small additions to
    String, Stream, Math,
    CompletableFuture, HTTP/2 API

As well as lots of internal refactorings.

Continuous improvements

Usability:

Tooling:

Continuous improvements

Performance:

  • auto-generated CDS ⑲ (JDK-8261455)

  • dynamic CDS archives ⑬ (JEP 350)

  • default CDS archives ⑫ (JEP 341)

  • continuous improvements in all garbage collectors

Security:

Cleaning house

Deprecations (for removal):

Already removed:

Migrations

To ease migrations:

  • stick to supported APIs

  • stick to standardized behavior

  • stick to well-maintained projects

  • keep dependencies and tools up to date

  • stay ahead of removals (jdeprscan)

  • build on each release (including EA)

Then you, too, can enjoy these projects ASAP!

Adoption

  • Java 11 is slowly but resolutely overtaking Java 8

  • adoption of 17 (from 11) looks good

  • always using latest is uncommon but persistent

Project Loom

JVM features and APIs for supporting easy-to-use, high-throughput, lightweight concurrency and new programming models

Profile:

Motivation

Imagine a hypothetical HTTP request:

  1. interpret request

  2. query database (blocks)

  3. process data for response

Resource utilization:

  • good for 1. and 3.

  • really bad for 2.

How to implement that request?

Synchronous

Align application’s unit of concurrency (request)
with Java’s unit of concurrency (thread):

  • use thread per request

  • simple to write, debug, profile

  • blocks threads on certain calls

  • limited number of platform threads
    ⇝ bad resource utilization
    ⇝ low throughput

Asynchronous

Only use threads for actual computations:

  • use non-blocking APIs
    (with futures / reactive streams)

  • harder to write, challenging to debug/profile

  • incompatible with synchronous code

  • shares platform threads
    ⇝ great resource utilization
    ⇝ high throughput

Motivation

Resolve the conflict between:

  • simplicity

  • throughput

Enter virtual threads!

A virtual thread:

  • is a regular Thread

  • low memory footprint ([k]bytes)

  • small switching cost

  • scheduled by the Java runtime

  • requires no OS thread when waiting

Virtual thread management

The runtime manages virtual threads:

  • runs them on a pool of carrier threads

  • makes them yield on blocking calls
    (frees the carrier thread!)

  • continues them when calls return

Virtual thread example

Remember the hypothetical request:

  1. interpret request

  2. query database (blocks)

  3. process data for response

In a virtual thread:

  • runtime submits task to carrier thread pool

  • when 2. blocks, virtual thread yields

  • runtime hands carrier thread back to pool

  • when 2. unblocks, runtime resubmits task

  • virtual thread continues with 3.

Example

try (var executor = Executors
		.newVirtualThreadPerTaskExecutor()) {
	IntStream
		.range(0, 1_000_000)
		.forEach(number -> {
			executor.submit(() -> {
				Thread.sleep(Duration.ofSeconds(1));
				return number;
			});
		});
} // executor.close() is called implicitly, and waits

Example

void handle(Request request, Response response)
		throws InterruptedException {
	try (var executor = Executors
			.newVirtualThreadPerTaskExecutor()) {
		var futureA = executor.submit(this::taskA);
		var futureB = executor.submit(this::taskB);
		response.send(futureA.get() + futureB.get());
	} catch (ExecutionException ex) {
		response.fail(ex);
	}
}

Performance

Virtual threads aren’t "faster threads":
Each task takes the same time (same latency).

So why bother?

Parallelism vs concurrency

ParallelismConcurrency

Task origin

solution

problem

Control

developer

environment

Resource use

coordinated

competitive

Metric

latency

throughput

Abstraction

CPU cores

tasks

# of threads

# of cores

# of tasks

Performance

When workload is not CPU-bound:

  • start waiting as early as possible

  • for as many tasks as possible

⇝ Virtual threads increase throughput:

  • when number of concurrent tasks is high

  • when workload is not CPU-bound

Use Cases

Virtual threads are cheap and plentiful:

  • no pooling necessary

  • allows thread per task

  • allows liberal creation
    of threads for subtasks

⇝ Enables new concurrency programming models.

Structured concurrency

Structured programming:

  • prescribes single entry point
    and clearly defined exit points

  • influenced languages and runtimes

Simlarly, structured concurrency prescribes:

When the flow of execution splits into multiple concurrent flows, they rejoin in the same code block.

Structured concurrency

When the flow of execution splits into multiple concurrent flows, they rejoin in the same code block.

⇝ Threads are short-lived:

  • start when task begins

  • end on completion

⇝ Enables parent-child/sibling relationships
and logical grouping of threads.

Unstructured concurrency

void handle(Request request, Response response)
		throws InterruptedException {
	try (var executor = Executors
			.newVirtualThreadPerTaskExecutor()) {
		// what's the relationship between
		// this and the two spawned threads?
		// what happens when one of them fails?
		var futureA = executor.submit(this::taskA);
		var futureB = executor.submit(this::taskB);
		// what if we only need the faster one?
		response.send(futureA.get() + futureB.get());
	} catch (ExecutionException ex) {
		response.fail(ex);
	}
}

Structured concurrency

void handle(Request request, Response response)
		throws InterruptedException {
	// define explicit success/error handling
	try (var scope = new StructuredTaskScope
							.ShutdownOnFailure()) {
		var futureA = scope.fork(this::taskA);
		var futureB = scope.fork(this::taskB);
		// wait explicitly until success criteria met
		scope.join();
		scope.throwIfFailed();

		response.send(futureA.get() + futureB.get());
	} catch (ExecutionException ex) {
		response.fail(ex);
	}
}

Structured concurrency

  • forked tasks are children of the scope

  • creates relationship between threads

  • success/failure policy can be defined
    across all children

Project Loom

Virtual threads:

  • code is simple to write, debug, profile

  • high throughput

  • new programing model

Structured concurrency:

  • clearer concurrency code

  • simpler failure/success policies

  • better debugging

Timeline

My personal (!) guess (!!):

JDK 20 (2023)
  • virtual threads (2nd preview; JEP 436)

  • structured concurrency (2nd incubator; JEP 437)

2024
  • more structured concurrency APIs

Deeper Dives

So long…​

37% off with
code fccparlog

bit.ly/the-jms

More

Slides at slides.nipafx.dev
⇜ Get my book!

Follow Nicolai

nipafx.dev
/nipafx

Follow Java

inside.java // dev.java
/java    //    /openjdk

Image Credits