public sealed interface Page
permits GitHubIssuePage, GitHubPrPage,
ExternalPage, ErrorPage {
// ...
}
this session goes over Java 18-21
we can’t cover all details
⇝ go to youtube.com/@java for more
slides at slides.nipafx.dev/java-x
(hit "?" to get navigation help)
ask questions any time
Part I:
pattern matching
virtual threads
string templates
Part II:
on-ramping to Java
sequenced collections
misc API improvements
GC, CDS, UTF-8
Part III:
Source and Classfile Tools
Development and Deployment Tools
Security Tools
Monitoring Tools
Costs of running on old versions:
support contract for Java
waning support in libraries / frameworks
Costs of not running on new versions:
lower productivity
less observability and performance
(more on that later)
less access to talent
bigger upgrade costs
Resistence is futile.
Preparations:
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 many JDK versions
Prepare by building on multiple JDK versions:
your baseline version
every supported version since then
latest version
EA build of next version
It’s not necessary to build …
… each commit on all versions
… the whole project on all versions
Build as much as feasible.
Within OpenJDK, there is no LTS.
⇝ has no impact on features, reliability, etc.
It’s a vendor-centric concept
to offer continuous fixes
(usually for money).
You’re paying not to get new features.
Pattern Matching |
A New Dynamic Dispatch |
Data-Oriented Programming |
Virtual Threads |
String Templates |
Features:
scrapes GitHub projects
creates Page
instances:
GitHubIssuePage
GitHubPrPage
ExternalPage
ErrorPage
further processes pages
Features:
display as interactive graph
compute graph properties
categorize pages by topic
analyze mood of interactions
process payment for analyses
etc.
How to implement features?
methods on Page
😧
visitor pattern 😫
pattern matching 🥳
Approach:
make Page
sealed
implement features as methods outside of Page
accept Page
parameters and switch
over it
avoid default
branch for maintainability
Sealed types limit inheritance,
by only allowing specific subtypes.
public sealed interface Page
permits GitHubIssuePage, GitHubPrPage,
ExternalPage, ErrorPage {
// ...
}
public void categorize(Page page) {
switch (page) {
case GitHubIssuePage is -> categorizeIssue(is);
case GitHubPrPage pr -> categorizePr(pr);
case ExternalPage ext -> categorizeExternal(ext);
case ErrorPage err -> categorizeError(err);
}
}
Unlike an if
-else
-if
-chain,
a pattern switch
needs to be exhaustive.
Fulfilled by:
switching over a sealed types
a case
per subtype
avoiding the default
branch
⇝ Adding a new subtype causes compile error!
Sometimes you have "defaulty" behavior:
public void categorize(Page page) {
switch (page) {
case GitHubIssuePage is -> categorizeIssue(is);
case GitHubPrPage pr -> categorizePr(pr);
default -> { }
}
}
But we need to avoid default
!
Write explicit branches:
public void categorize(Page page) {
switch (page) {
case GitHubIssuePage is -> categorizeIssue(is);
case GitHubPrPage pr -> categorizePr(pr);
// duplication 😢
case ErrorPage err -> { };
case ExternalPage ext -> { };
};
}
Use _
to combine "default branches":
public void categorize(Page page) {
switch (page) {
case GitHubIssuePage is -> categorizeIssue(is);
case GitHubPrPage pr -> categorizePr(pr);
case ErrorPage _, ExternalPage _ -> { };
};
}
⇝ Default behavior without default
branch.
Dynamic dispatch selects the invoked method by type.
As language feature:
via inheritance
makes method part of API
What if methods shouldn’t be part of the API?
Without methods becoming part of the API.
Via visitor pattern:
makes "visitation" part of API
cumbersome and indirect
Via pattern matching (new):
makes "sealed" part of type
straight-forward
More on pattern matching:
Pattern Matching |
A New Dynamic Dispatch |
Data-Oriented Programming |
Virtual Threads |
String Templates |
Paradigms often come with an
"Everything is a …" sentence.
The goal of any programming paradigm is to manage complexity.
complexity comes in many forms
not all paradigms handle all forms equally well
⇝ "It depends"
Everything is an object
combines state and behavior
hinges on encapsulation
polymorphism through inheritance
Works best when defining/upholding boundaries.
Great use cases for OOP:
boundaries between libraries and clients
in large programs to enable modular reasoning
Consider a data-oriented approach for:
smaller (sub)systems
focused on data
Guiding principles:
model the data, the whole data,
and nothing but the data
data is immutable
validate at the boundary
make illegal states unrepresentable
From Brian Goetz' seminal article:
Data Oriented Programming in Java
The app we just looked at:
is small
focusses on data (Page
)
separates operations from types
⇝ Perfect for data-oriented programming!
Model the data, the whole data,
and nothing but the data.
There are four kinds of pages:
error page
external page
GitHub issue page
GitHub PR page
⇝ Use four records to model them!
public record ErrorPage(
URI url, Exception ex) { }
public record ExternalPage(
URI url, String content) { }
public record GitHubIssuePage(
URI url, String content,
int issueNumber, Set<Page> links) { }
public record GitHubPrPage(
URI url, String content,
int prNumber, Set<Page> links) { }
Model the data, the whole data,
and nothing but the data.
There are additional relations between them:
a page (load) is either successful or not
a successful page is either external or GitHub
a GitHub page is either for a PR or an issue
⇝ Use sealed types to model the alternatives!
public sealed interface Page
permits ErrorPage, SuccessfulPage {
URI url();
}
public sealed interface SuccessfulPage
extends Page permits ExternalPage, GitHubPage {
String content();
}
public sealed interface GitHubPage
extends SuccessfulPage
permits GitHubIssuePage, GitHubPrPage {
Set<Page> links();
default Stream<Page> subtree() { ... }
}
records are product types
sealed types are sum types
This simple combination of mechanisms — aggregation and choice — is deceptively powerful
Make illegal states unrepresentable.
Many are already, e.g.:
with error
and with content
with issueNumber
and prNumber
with isseNumber
or prNumber
but no links
Validate at the boundary.
⇝ Reject other illegal states in constructors.
record ExternalPage(URI url, String content) {
// compact constructor
ExternalPage {
Objects.requireNonNull(url);
Objects.requireNonNull(content);
if (content.isBlank())
throw new IllegalArgumentException();
}
}
Data is immutable.
Records are shallowly immutable,
but field types may not be.
⇝ Fix that during construction.
// compact constructor
GitHubPrPage {
// [...]
links = Set.copyOf(links);
}
page "type" is explicit in Java’s type
only legal combination of data are possible
API is self-documenting
code is trivial to test
But where did the operations go?
Model the data, the whole data,
and nothing but the data.
⇝ Methods should be limited to derived quantities.
public record GitHubIssuePage(
URI url, String content,
int issueNumber, Set<Page> links) {
public String toPrettyString() {
return "🐈 ISSUE #" + issueNumber;
}
}
Other operations must be defined elsewhere:
methods in other subsystems
use pattern matching over sealed types
for polymorphic operations
avoid default branch
use record patterns to access data
⇝ This is just pattern matching.
If toPrettyString
is defined outside of Page
:
private static String toPrettyString(Page page) {
return switch (page) {
case ErrorPage(var url, _)
-> "💥 ERROR: " + url.getHost();
case ExternalPage(var url, _)
-> "💤 EXTERNAL: " + url.getHost();
case GitHubIssuePage(_, _, int issueNumber, _)
-> "🐈 ISSUE #" + issueNumber;
case GitHubPrPage(_, _, int prNumber, _)
-> "🐙 PR #" + prNumber;
};
}
immutable data structures
methods (functions?) that operate on them
Isn’t this just functional programming?!
Kind of.
Functional programming:
Everything is a function
⇝ Focus on creating and composing functions.
Data-oriented programming:
Model data as data.
⇝ Focus on correctly modeling the data.
OOP is not dead (again):
valuable for complex entities or rich libraries
use whenever encapsulation is needed
still a good default on high level
DOP — consider when:
mainly handling outside data
working with simple or ad-hoc data
data and behavior should be separated
Use Java’s strong typing to model data as data:
use classes to represent data, particularly:
data as data with records
alternatives with sealed classes
use methods (separately) to model behavior, particularly:
exhaustive switch
without default
pattern matching to destructure polymorphic data
model the data, the whole data,
and nothing but the data
data is immutable
validate at the boundary
make illegal states unrepresentable
More on data-oriented programming:
📝 Data Oriented Programming in Java (Brian Goetz)
🎥 Java 21 Brings Full Pattern Matching (Sep 2023)
🎥 Data-Oriented Programming (Jul 2022)
🧑💻 GitHub crawler
Pattern Matching |
Virtual Threads |
Unlimited Threads |
Under The Hood |
String Templates |
Imagine a hypothetical HTTP request:
interpret request
query database (blocks)
process data for response
Resource utilization:
good for 1. and 3.
really bad for 2.
How to implement that request?
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
Only use threads for actual computations:
use non-blocking APIs (futures / reactive streams)
harder to write, challenging to debug/profile
incompatible with synchronous code
shares platform threads
⇝ great resource utilization
⇝ high throughput
There’s a conflict between:
simplicity
throughput
There are other conflicts:
design vs performance (⇝ Valhalla)
explicitness vs succinctness (⇝ Amber)
flexibility vs safety (⇝ Panama)
dynamism vs performance (⇝ Leyden)
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 memory:
maps large virtual address space
to limited physical memory
gives illusion of plentiful memory
Virtual threads:
map large number of virtual threads
to a small number of OS threads
give the illusion of plentiful threads
Programs rarely care about virtual vs physical memory.
Programs need rarely care about virtual vs platform thread.
Instead:
write straightforward (blocking) code
runtime shares available OS threads
reduces the cost of blocking to near zero
try (var executor = Executors
.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 1_000_000; i++) {
var number = i;
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return number;
});
}
} // executor.close() is called implicitly, and waits
Virtual threads:
remove "number of threads" as bottleneck
match app’s unit of concurrency to Java’s
⇝ simplicity && throughput
Virtual threads aren’t "faster threads":
same number of CPU cycles
each task takes the same time (same latency)
So why bother?
Parallelism | Concurrency | |
---|---|---|
Task origin | solution | problem |
Control | developer | environment |
Resource use | coordinated | competitive |
Metric | latency | throughput |
Abstraction | CPU cores | tasks |
# of threads | # of cores | # of tasks |
When workload is not CPU-bound:
start waiting as early as possible
for as many tasks as possible
⇝ Virtual threads increase throughput:
when workload is not CPU-bound
when number of concurrent tasks is high
For servers:
request handling threads are started by web framework
frameworks will offer (easy) configuration options
We’re getting there.
Annotate request handling method on 3.?:
@GET
@Path("api")
@RunOnVirtualThread
public String handle() {
// ...
}
(Source)
Just works on 4.0 (currently RC1).
(Source)
Go forth and multiply (your threads)
Pattern Matching |
Virtual Threads |
Unlimited Threads |
Under The Hood |
String Templates |
Virtual threads:
always work correctly
may not scale perfectly
Code changes can improve scalability
(and maintainability, debuggability, observability).
Only pool expensive resources
but virtual threads are cheap.
⇝ Replace thread pools (for concurrency),
with virtual threads plus, e.g., semaphores.
// limits concurrent queries but pools 👎🏾
private static final ExecutorService DB_POOL =
Executors.newFixedThreadPool(16);
public <T> Future<T> queryDatabase(Callable<T> query) {
return DB_POOL.submit(query);
}
// limits concurrent queries without pool 👍🏾
private static final Semaphore DB_SEMAPHORE =
new Semaphore(16);
public <T> T queryDatabase(Callable<T> query)
throws Exception {
DB_SEMAPHORE.acquire();
try {
return query.call();
} finally {
DB_SEMAPHORE.release();
}
}
To understand virtual thread caveats
we need to understand how they work.
(Also, it’s very interesting.)
The Java runtime manages virtual threads:
runs them on a pool of carrier threads
on blocking call:
internally calls non-blocking operation
unmounts from carrier thread!
when call returns:
mounts to (other) carrier thread
continues
Remember the hypothetical request:
interpret request
query database (blocks)
process data for response
In a virtual thread:
runtime submits task to carrier thread pool
when 2. blocks, virtual thread unmounts
runtime hands carrier thread back to pool
when 2. unblocks, runtime resubmits task
virtual thread mounts and continues with 3.
Virtual threads work correctly with everything:
all blocking operations
synchronized
Thread
, currentThread
, etc.
thread interruption
thread-locals
native code
But not all scale perfectly.
Some operations pin (operations don’t unmount):
native method call (JNI)
foreign function call (FFM)
synchronized
block (for now)
⇝ No compensation
⚠️ Problematic when:
pinning is frequent
contains blocking operations
If possible:
avoid pinning operations
remove blocking operations
from pinning code sections.
// guarantees sequential access, but pins (for now) 👎🏾
public synchronized String accessResource() {
return access();
}
// guarantees sequential access without pinning 👍🏾
private static final ReentrantLock LOCK =
new ReentrantLock();
public String accessResource() {
// lock guarantees sequential access
LOCK.lock();
try {
return access();
} finally {
LOCK.unlock();
}
}
Thread-locals can hinder scalability:
can be inherited
to keep them thread-local,
values are copied
can occupy lots of memory
(There are also API shortcomings.)
⇝ Refactor to scoped values (JEP 446).
// copies value for each inheriting thread 👎🏾
static final ThreadLocal<Principal> PRINCIPAL =
new ThreadLocal<>();
public void serve(Request request, Response response) {
var level = request.isAdmin() ? ADMIN : GUEST;
var principal = new Principal(level);
PRINCIPAL.set(principal);
Application.handle(request, response);
}
// immutable, so no copies needed 👍🏾
static final ScopedValue<Principal> PRINCIPAL =
new ScopedValue<>();
public void serve(Request request, Response response) {
var level = request.isAdmin() ? ADMIN : GUEST;
var principal = new Principal(level);
ScopedValue
.where(PRINCIPAL, principal)
.run(() -> Application
.handle(request, response));
}
Most importantly:
replace thread pools with semaphores
Also helpful:
remove long-running I/O from pinned sections
replace thread-locals with scoped values
replace synchronized
with locks
Pattern Matching |
Virtual Threads |
String Templates |
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! 😳
Why not?
// (fictional syntax!)
String query =
"SELECT * FROM Person p "
+ "WHERE p.\{property} = '\{value}'";
Also comes with free SQL injection! 😳
SQL injections aren’t the only concern.
These also need validation and sanitization:
HTML/XML
JSON
YAML
…
All follow format-specific rules.
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
String form = FMT."""
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
Often, strings are just exchange format, e.g.:
start with: String
+ values
validate / sanitize (i.e. parse)
dumb down to: String
🤔
parse to: JSONObject
, Statement
, …
Why the detour?
STR
is a singleton instance of
a Processor
implementation:
public interface Processor<RESULT, EX> {
RESULT process(StringTemplate s) throws EX;
}
RESULT
can be of any type!
// prevents SQL injections
Statement query = SQL."""
SELECT * FROM Person p
WHERE p.\{property} = '\{value}'
""";
// validates & escapes JSON
JSONObject doc = JSON."""
{
"name": "\{name}",
"year": "\{bday.getYear()}"
}
""";
String templates:
simplify string concatenation
enable domain-specific processing
incentivize the "right way"
We’ll see you back here in 15 minutes with Part II:
on-ramping to Java
sequenced collections
misc API improvements
GC, CDS, UTF-8
On-Ramp |
Sequenced Collections |
Misc. API Improvements |
Generational ZGC |
Class-Data Sharing |
UTF-8 by Default |
Java 21 makes life easier
for new (Java) developers.
We all know Java, IDEs, build tools, etc.
do we all?
what about your kids?
what about students?
what about the frontend dev?
what about ML/AI folks?
Java needs to be approachable!
Java needs an on-ramp for new (Java) developers!
To write and run a simple Java program, you need:
a JDK
an editor (IDE?)
javac
(build tool? IDE?)
java
(IDE?)
some Java code
Minimal Java code:
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
visibility
classes & methods
static vs instance
returns & parameters
statements & arguments
That’s a lot of tools and concepts!
Java is great for large-scale development:
detailed toolchain
refined programming model
This make it less approachable.
Let’s change that!
Java 9 added jshell
:
all you need:
tools: JDK, jshell
concepts: statements & arguments
but:
not great for beginners (IMO)
no progression
More is needed.
Java 11 added single-file execution (JEP 330):
java Prog.java
removed: javac
but: no progression
Much better for beginners,
but just a section of an on-ramp.
Expand single-file execution in two directions:
simplify code: reduce required Java concepts
ease progression: run multiple files with java
Remove requirement of:
String[] args
parameter
main
being static
main
being public
the class itself
// all the code in Prog.java
void main() {
System.out.println("Hello, World!");
}
[Preview in Java 21 — JEP 445]
Say you have a folder:
MyFirstJava
├─ Prog.java
├─ Helper.java
└─ Lib
└─ library.jar
Run with:
java -cp 'Lib/*' Prog.java
[Preview in Java 22 — JEP 458]
Natural progression:
start with main()
need arguments? ⇝ add String[] args
need to organize code? ⇝ add methods
need shared state? ⇝ add fields
need more functionality? ⇝ explore JDK APIs
even more? ⇝ explore simple libraries
need more structure? ⇝ split into multiple files
even more ⇝ use visibility & packages
Doesn’t even have to be that order!
Java’s strengths for large-scale development
make it less approachable:
detailed toolchain
refined programming model
There are new features that:
make it easier to start
allow gradual progression
entice the future dev generation
📝 JEP 445 for a simpler main
📝 JEP 458 for launching multiple source file
🎥 Script Java Easily in 21 and Beyond (May 2023)
On-Ramp |
Sequenced Collections |
Misc. API Improvements |
Generational ZGC |
Class-Data Sharing |
UTF-8 by Default |
Collections with order and indexed access:
List
Collections with order without indexed access:
SortedSet
(sort order)
Deque
(insertion order)
LinkedHashSet
(insertion order)
and more
New interfaces capture the concept of order:
SequencedCollection
SequencedSet
SequencedMap
Use as parameter or return type
and enjoy new methods.
Getting the first element:
list.get(0)
sortedSet.first()
deque.getFirst()
linkedHashSet.iterator().next()
Now for all:
sequencedCollection.getFirst()
Removing the last element:
list.remove(list.size() - 1)
sortedSet.remove(sortedSet.last())
deque.removeLast()
linkedHashSet.🤷🏾♂️()
Now for all:
sequencedCollection.removeLast()
Reversing order:
list.listIterator()
⇝ ListIterator
navigableSet.descendingSet()
⇝ NavigableSet
(view)
deque.descendingIterator()
⇝ Iterator
linkedHashSet.🤷🏾♂️()
Now for all:
sequencedCollection.reversed()
sequencedCollection.reversed()
returns
a SequencedCollection
view:
for (E element : list.reversed())
// ...
sortedSet
.reversed().stream()
//...
deque.reversed().toArray();
sequencedCollection.reversed()
returns
a SequencedCollection
view:
var letters = new ArrayList<>(List.of("A", "B", "C"));
// ⇝ letters = ["A", "B", "C"]
letters.reversed().removeFirst();
// ⇝ letters = ["A", "B"]
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
SequencedCollection<E> reversed();
(Analoguous for maps.)
What happens when addFirst|Last
is used
on a sorted data structure?
SortedSet<String> letters = new TreeSet<>(
List.of("B", "A", "C"));
// ⇝ letters = ["A", "B", "C"]
letters.addLast("D");
Options:
works always ⇝ breaks SortedSet
contract
works if value fits ⇝ hard to predict
works never ⇝ UnsupportedOperationException
Use the most general type that:
has the API you need/support
plays the role you need/support
For collections, that’s often: Collection
(less often: List
, Set
).
⇝ Consider new types!
📝 JEP 431: Sequenced Collections
🎥 Java 21’s New (Sequenced) Collections (Mar 2023)
On-Ramp |
Sequenced Collections |
Misc. API Improvements |
Generational ZGC |
Class-Data Sharing |
UTF-8 by Default |
How do you create an ArrayList
that
can store 50 elements without resizing?
new ArrayList<>(50);
How do you create a HashMap
that
can store 50 pairs without resizing?
new HashMap<>(64, 0.8f);
new HashMap<>(128);
🤔
Right-sizing hashing data structures:
HashMap.newHashMap(int numMappings)
HashSet.newHashSet(int numElements)
LinkedHashMap.newLinkedHashMap(int numMappings)
LinkedHashSet.newLinkedHashSet(int numElements)
Lots of new methods on Math
:
for int
/long
division with different modes for:
rounding
overflow handling
for ceiling modulus (5 ⌈%⌉ 3 = -1
)
for clamping
"String".indexOf(
String str, int beginIndex, int endIndex)
On StringBuilder
and StringBuffer
:
repeat(int codePoint, int count)
repeat(CharSequence cs, int count)
On Character
(all static
):
isEmoji(int codePoint)
isEmojiPresentation(int codePoint)
isEmojiModifier(int codePoint)
isEmojiModifierBase(int codePoint)
isEmojiComponent(int codePoint)
Options for formatting dates/times with DateTimeFormatter
:
with a fixed pattern: ofPattern
with a localized style: ofLocalizedDate
(FULL
, LONG
, MEDIUM
, SHORT
)
What about a localized result with custom elements?
⇝ DateTimeFormatter.ofLocalizedPattern
DateTimeFormatter.ofLocalizedPattern
:
you include what you want to show up
(e.g. year + month with "yMM"
)
result will depend on locale
(e.g. "10/2023"
in USA)
var now = ZonedDateTime.now();
for (var locale : List.of(
Locale.of("en", "US"),
Locale.of("be", "BE"),
Locale.of("vi", "VN"))) {
Locale.setDefault(locale);
var custom = DateTimeFormatter
.ofPattern("y-MM-dd");
var local = DateTimeFormatter
.ofLocalizedDate(FormatStyle.SHORT);
var customLocal = DateTimeFormatter
.ofLocalizedPattern("yMM");
// pretty print
}
locale | custom | local | both |
---|---|---|---|
en_US |
|
|
|
be_BE |
|
|
|
vi_VN |
|
|
|
Analogue methods were added to DateTimeFormatterBuilder
:
DateTimeFormatterBuilder appendLocalized(
String requestedTemplate);
static String getLocalizedDateTimePattern(
String requestedTemplate,
Chronology chrono, Locale locale)
These types now implemnet AutoCloseable
:
HttpClient
ExecutorService
ForkJoinPool
Additions to Future<T>
:
T resultNow()
Throwable exceptionNow
State state()
New:
enum State {
RUNNING, SUCCESS, FAILED, CANCELLED
}
There are many more additions like this.
Find a few more in
🎥 Java 21 API New Features (Sep 2023)
On-Ramp |
Sequenced Collections |
Misc. API Improvements |
Generational ZGC |
Class-Data Sharing |
UTF-8 by Default |
Compared to other GCs, ZGC:
optimizes for ultra-low pause times
can have higher memory footprint or higher CPU usage
In Java 21, ZGC becomes generational.
most objects die young
those who don’t, grow (very) old
GCs can make use of this by tracking
young and old generations.
ZGC didn’t do this, but can do it now with:
-XX:+UseZGC -XX:+ZGenerational
A Cassandra 4 benchmark of ZGC vs GenZGC showed:
4x throughput with a fixed heap or
1/4x heap size with stable throughput
(Probably not representative but very promising.)
📝 JEP 439: Generational ZGC
🎥 Generational ZGC and Beyond (Aug 2023)
🎥 Java’s Highly Scalable Low-Latency GC: ZGC (Mar 2023)
On-Ramp |
Sequenced Collections |
Misc. API Improvements |
Generational ZGC |
Class-Data Sharing |
UTF-8 by Default |
Use CDS to shave off 10-25% of your boot times.
Recent improvements:
default CDS archives ⑫ (JEP 341)
dynamic CDS archives ⑬ (JEP 350)
auto-generated CDS ⑲ (JDK-8261455)
And more to come from Project Leyden.
On-Ramp |
Sequenced Collections |
Misc. API Improvements |
Generational ZGC |
Class-Data Sharing |
UTF-8 by Default |
[Since Java 18 — JEP 400]
All Java APIs use UTF-8 as default charset.
This change concerns projects that:
run on Windows
use non UTF-8 encoded files
do not pass a charset to APIs
In that case, you need to update code!
What it is:
a component that transforms byte code
uses java.lang.instrument
or JVM TI
launches with JVM or attaches later ("dynamic")
What you need to know:
all mechanisms for agents remain intact
nothing changed yet
in the future, dynamic attach will be
disabled by default
enable with -XX:+EnableDynamicAgentLoading
What you need to do:
run your app with -XX:-EnableDynamicAgentLoading
observe closely
investigate necessity of dynamic agents
What it is:
finalize()
methods
a JLS/GC machinery for them
What you need to know:
you can disable with --finalization=disabled
in a future release, disabled
will be the default
in a later release, finalization will be removed
What you need to do:
search for finalize()
in your code and
replace with try
-with-resources or Cleaner
API
search for finalize()
in your dependencies and
help remove them
run your app with --finalization=disabled
and
closely monitor resource behavior (e.g. file handles)
What it is:
a system of checks and permissions
intended to safeguard security-relevant
code sections
embodied by SecurityManager
What you need to know:
barely used but maintenance-intensive
already disallowed by default
enable with java.security.manager=allow
in a future release, it will be removed
What you need to do:
observe your app with default settings
(⇝ security manager disallowed)
if used, move away from security manager
What it is:
new Integer(42)
new Double(42)
etc.
What you need to know:
Valhalla wants to turn them into value types
those have no identity
identity-based operations need to be removed
What you need to do:
Integer.valueOf(42)
Double.valueOf(42)
etc.
📝 all the aforementioned JEPs
We’ll see you back here in 15 minutes with Part III:
Source and Classfile Tools
Development and Deployment Tools
Security Tools
Monitoring Tools
Source and Classfile Tools |
Development and Deployment Tools |
Security Tools |
Monitoring Tools |
The tools you use to create and build Java programs.
Literal constants are folded.
String concatenation is folded.
Constant fields are inlined.
Dead code branches are eliminated.
These do not require any flag and are part
of the compiler specification before JDK 17.
Before JDK 19
public class Example {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("foo");
System.err.println("" + sb + sb.append("bar"));
}
}
// prints foobarfoobar
After JDK 19, string concatenation evaluates each argument and eagerly converts it to a string, in left-to-right order.
public class Example {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("foo");
System.err.println("" + sb + sb.append("bar"));
}
}
// prints foofoobar
[Indy String Concat Changes Order of Operations — JDK-8273914]
Evaluation error when compiling:
public class Example {
public static void main(String[] args) {
long value = 5L;
long b = value + 0.1 * 3L;
}
}
// incompatible types: possible lossy conversion
// from double to long
Before JDK 20, no error is produced when compiling:
public class Example {
public static void main(String[] args) {
long b = 5L;
b += 0.1 * 3L;
}
}
After JDK 20, get a warning when compiling:
javac Example.java -Xlint:all
Example.java:4: warning:
[lossy-conversions] implicit cast from double
to long in compound assignment is possibly lossy
b += 0.1 * 3L;
^
1 warning
[Warn compound assignment is possibly lossy — JDK-8244681]
DO NOT call overridable methods from a constructor!
public class LintExample {
public LintExample() {
System.out.println(this.hashCode());
}
public static void main(String[] args) {
new LintExample();
}
}
Before JDK 21, LintExample.java
compiles successfully
javac LintExample.java -Xlint:all
The compiler has a new -Xlint:this-escape
key:
javac LintExample.java -Xlint:[all|this-escape]
LintExample.java 3: warning:
[this-escape] possible 'this' escape before subclass
is fully initialized
System.out.println(this.hashCode()); ^
1 warning
[New lint check key ㉑ — JDK-8015831]
Helps with product maintenance.
Technical users can understand your APIs goals.
Can increase awareness/adoption of your software.
Third-party developers can start quickly by trying out API examples.
Wrap code examples inside <pre>
and {@code …}
Automatically escape special characters with {@code …}
Little control over indentation
No code highlighting
JEP 413 introduced {@snippet …}
tag in JDK 18.
A better presentation of the code examples via regions.
Control code via @highlight, @replace, @link
tags and regions.
JEP 413 introduced {@snippet …}
tag in JDK 18.
A better presentation of the code examples via regions.
Control code via @highlight,@replace,@link
tags and regions.
The tag accepts separate files that hold snippet content.
$ javadoc # other options..
--snippet-path ./src/xml User.java
Configure the --add-script <file>
option of javadoc
cat interact.js
alert("Get ready to move your fingers!");
javadoc -d docs # other options..........
--add-script interact.js User.java
Use the option to add multiple scripts in your generated documentation.
Core JDK tools tutorials
JDK-8275786 ⑱ about adding scripts to generated docs
JEP 413 about code snippets in documentation
Source and Classfile Tools |
Development and Deployment Tools |
Security Tools |
Monitoring Tools |
Quickly try, debug and learn Java and its APIs.
Experiment with Java by bypassing the compile stage.
Get immediate feedback on your code.
Exposes a programmatic API
jshell> var check = new Boolean("true");
| Warning:
| Boolean(java.lang.String) in java.lang.Boolean
| has been deprecated and marked for removal
| var check = new Boolean("true");
| ^-----------------^
check ==> true
[jshell outlines deprecated elements ⑲ — JDK-8274148]
jshell
supports loading scripts.
Scripts can be local files or one of the predefined scripts.
Scripts may hold any valid code snippets or jshell
commands.
Script | Description |
---|---|
DEFAULT | Loads the default entries, which are commonly used as imports. |
JAVASE | Imports all Java SE packages. |
PRINTING | Defines |
TOOLING ㉑ | Defines |
When you start jshell
jshell TOOLING
Inside a jshell
session
jshell> /open TOOLING
[JDK tools in jshell ㉑ — JDK-8306560]
Check available tools
jshell> tools()
Execute a tool command in a jshell
session
jshell> run("javac", "-version")
jshell> javac("-version")
javac 21
[Introducing jwebserver ⑱ — JEP 408]
Web development testing, to simulate locally a client-server setup.
jwebserver
Binding to loopback by default.
For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving /cwd and subdirectories on 127.0.0.1 port 8000
URL: http://127.0.0.1:8000/
Web-service prototyping or application testing.
jwebserver -d api -p 9000
Binding to loopback by default.
For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving /api and subdirectories on 127.0.0.1 port 9000
URL http://127.0.0.1:9000/
127.0.0.1 - - [01/Feb/2024:14:27:26 +0100]
"GET /activity.json HTTP/1.1" 200 -
Search a directory on a remote server from your local machine.
jwebserver -b 0.0.0.0
Serving /work and subdirectories
on 0.0.0.0 (all interfaces) port 8000
URL http://192.168.178.41:8000/
The SimpleFileServer
class supports the creation of:
file server
file handler
an output filter
[Working with the Simple Web Server — inside.java article]
Let’s assume the following project structure:
src/main/java
├── module-info.java
└── org
└── example
└── HelloWorldFX.java
cat src/main/java/module-info.java
module hellofx {
requires javafx.controls;
exports org.example;
}
jmod
creates module files that encapsulate a set of
compiled Java classes, resources and other related files.
jmod create --class-path mods/helloworldfx \
--main-class org.example.HelloWorldFX \
--module-version 1.0.0 \
--compress zip-9 hellofx.jmod
Use --compress
command line option:
accepts values between zip-[0-9]
zip-0
provides no compression,
zip-9
provides the best compression
the default compression value is zip-6
[jmod --compress option — JDK-8293499]
You can further create an optimal application image.
jlink --launcher hello=hellofx/example.HelloWorldFX \
--module-path $PATH_TO_JMODS:/hellofx.jmod \
--add-modules hellofx \
--output my-app
Packages self-contained Java applications.
Prior JDK19, installing a jpackaged app was system-wide.
For modular applications, jpackage
will automatically run jlink
and generate a runtime with the modules needed.
jpackage --input target/ --name JDemoApp \
--type app-image --main-jar JDemoApp.jar \
--main-class JDemoApp
JDemoApp.app/
Contents/
Info.plist
MacOS/ // Application launchers
JDemoApp
Resources/ // Icons, etc.
app/
JDemoApp.cfg // Config info, done by jpackage
JDemoApp.jar // copied from --input directory
runtime/ // Java runtime image
Application launcher will search for .cfg
in user-specific folders.
Linux
~/.local/${PACKAGE_NAME}
~/.${PACKAGE_NAME}
macOS
~/Library/Application Support/${PACKAGE_NAME}
Windows
%LocalAppData%\%PACKAGE_NAME%
%AppData%\%PACKAGE_NAME%
# ${PACKAGE_NAME} and %PACKAGE_NAME%
# refer to jpackaged application name.
Application launcher will look up the .cfg
file:
In user-specific directories,
From the installation directory if .cfg
file is not found,
From the application image directory if the application launcher is executed from application image.
Build a Java source browser with jshell API
Christian Stein’s article on jshell tooling
Tutorial on jwebserver
jwebserver ㉑ JEP 408
Julia Boes' article on jwebserver API examples
Tutorial on jpackage
How to package a JavaFX application with jpakckage
Source and Classfile Tools |
Development and Deployment Tools |
Security Tools |
Monitoring Tools |
You can check the version of keytool
and jarsigner
.
keytool -version & jarsigner -version
[1] 83711
jarsigner 21
keytool 21
[Add -version option to keytool and jarsigner ⑱ — JDK-8272163]
As of JDK 21, keytool
warns you when using weak password-based encryption algorithms via:
-genseckey
option
-importpass
option
[keytool warns about weak PBE algorithms ㉑ — JDK-8286907]
keytool -genseckey -alias secret -keypass changeit \
-keyalg RC4 -keysize 128 -keystore example.p12 \
-storepass changeit -storetype PKCS12 -v
Generated 128-bit ARCFOUR secret key
[Storing example.p12]
Warning:
The generated secret key uses the ARCFOUR algorithm
which is considered a security risk.
Removed in JDK 21: -altsigner
and -altsignerpath
As of JDK 19, specify the classpath for providers
via –providerPath
.
jarsigner -keystore keystore -storetype CUSTOMKS \
-providerPath /path/to/test.myks \
-providerClass my.custom.AnotherProvider \
signed.jar mykey
[Add -providerPath option to jarsigner ⑲ — JDK-8281175]
Security Tools tutorials
Java 21 Tool enhancements Better Across the Board
Monitoring Java Application Security with JDK tools and JFR Events
Source and Classfile Tools |
Development and Deployment Tools |
Security Tools |
Monitoring Tools |
Technology | Goal |
---|---|
JDK Flight Recorder (JFR) | Collects diagnostic and profiling data about a running Java application. |
JFR Event Streaming API | API for the continuous consumption of JFR data on disk. |
JDK Mission Control (JMC) | A set of tools for managing, monitoring, profiling, and troubleshooting Java applications. |
Start JFR a recording via -XX:StartFlightRecording
or jcmd
.
java -XX:StartFlightRecording -jar imaging.jar
Use the PID or jar name in the command
Note | Use jps JDK tool to list all running Java processes. |
[JFR view command ㉑ — JDK-8306704]
jdk.FinalizerStatistics
Identifies classes at runtime that use finalizers.
Enabled by default in default.jfc
and profile.jfc
.
No event is sent if java --finalization=disabled
.
[A finalization JFR event — JDK-8266936]
Disable via
jfr configure jdk.FinalizerStatistics#enabled=false
# or on launch
java -XX:StartFlightRecording:settings=none,
+jdk.FinalizerStatistics#enabled=false
jdk.InitialSecurityProperty
cryptographic event
Enabled by default in default.jfc
and profile.jfc
.
Captures details of initial security properties when loaded via the java.security.Security
class.
Disable via
jfr configure jdk.InitialSecurityProperty#enabled=false
# or on launch
java -XX:StartFlightRecording:settings=none,
+jdk.InitialSecurityProperty#enabled=false
jdk.SecurityProviderService
cryptographic event
Disabled by default in default.jfc
and profile.jfc
.
Records info on java.security.Provider.getService(…)
calls.
Enable via
jfr configure jdk.SecurityProviderService#enabled=true
# or on launch
java -XX:StartFlightRecording:settings=none,
+jdk.SecurityProviderService#enabled=true
Rendering large graphs is possible with smart pruning.
Aggregate Flame graph and Stack trace via Samples.
Dependency view presents aggregation of events using hierarchical edge bundling.
JDK Flight Recorder tutorials
Stack Walker ep 2 on JFR
JDK Mission Control 8.3 Sip of Java
Finding CPU Load with JFR Sip of Java
all other images are copyrighted