URL codefx = new URL("http://codefx.org");
URLConnection connection = codefx.openConnection();
Reader reader = new BufferedReader(
new InputStreamReader(
connection.getInputStream()));
this talk covers Java 9 to 11
it is a showcase, not a tutorial
slides at slides.codefx.org
Language Changes |
New and Updated APIs |
New JVM Features |
Performance Improvements |
Local-Variable Type Inference ⑩ |
Private Interface Methods ⑨ |
Try-With-Resources ⑨ |
Diamond Operator ⑨ |
SafeVarargs ⑨ |
Deprecation Warnings ⑨ |
Local-Variable Type Inference ⑩ |
Private Interface Methods ⑨ |
Try-With-Resources ⑨ |
Diamond Operator ⑨ |
SafeVarargs ⑨ |
Deprecation Warnings ⑨ |
Type inference with var
.
Less typing, but still strongly typed.
We’re used to duplicating
type information:
URL codefx = new URL("http://codefx.org");
URLConnection connection = codefx.openConnection();
Reader reader = new BufferedReader(
new InputStreamReader(
connection.getInputStream()));
Not so bad?
What about this?
No no = new No();
AmountIncrease<BigDecimal> more =
new BigDecimalAmountIncrease();
HorizontalConnection<LinePosition, LinePosition>
jumping =
new HorizontalLinePositionConnection();
Variable variable = new Constant(5);
List<String> names = List.of("Max", "Maria");
Can’t somebody else do that?
Compiler knows the types!
Enter var
:
var codefx = new URL("http://codefx.org");
var connection = codefx.openConnection();
var reader = new BufferedReader(
new InputStreamReader(
connection.getInputStream()));
var
not a keyword, but a reserved type name
⇝ variables/fields can be named var
only works for local variables, for
, try
⇝ no var
on fields or in method signatures
also on lambda parameters ⑪
⇝ annotate inferred type on lambda parameters
compiler infers type from right-hand side
⇝ rhs has to exist and define a type
compiler writes type into bytecode
⇝ no run-time component
This is about readability!
less redundancy
aligned variable names
more focus on variable names
more intermediate variables
var no = new No();
var more = new BigDecimalAmountIncrease();
var jumping = new HorizontalLinePositionConnection();
var variable = new Constant(5);
var names = List.of("Max", "Maria");
Still think omitting types is always bad?
Ever wrote a lambda without declaring types?
rhetoricalQuestion.answer(yes -> "see my point?");
var
First Contact With var
In Java 10
💻 tiny.cc/java-var /
▶ tiny.cc/java-var-yt
var
and …
Local-Variable Type Inference ⑩ |
Private Interface Methods ⑨ |
Try-With-Resources ⑨ |
Diamond Operator ⑨ |
SafeVarargs ⑨ |
Deprecation Warnings ⑨ |
Enabling reuse between default methods.
public interface InJava8 {
default boolean evenSum(int... numbers) {
return sum(numbers) % 2 == 0;
}
default boolean oddSum(int... numbers) {
return sum(numbers) % 2 == 1;
}
default int sum(int[] numbers) {
return IntStream.of(numbers).sum();
}
}
public interface InJava9 {
private int sum(int[] numbers) {
return IntStream.of(numbers).sum();
}
}
Just like private methods in abstract classes:
must be implemented
can not be overriden
can only be called in same source file
Local-Variable Type Inference ⑩ |
Private Interface Methods ⑨ |
Try-With-Resources ⑨ |
Diamond Operator ⑨ |
SafeVarargs ⑨ |
Deprecation Warnings ⑨ |
Making try
-with-resources blocks cleaner.
void doSomethingWith(Connection connection)
throws Exception {
try(Connection c = connection) {
c.doSomething();
}
}
Why is c
necessary?
c
necessary?target of close()
must be obvious
⇝ resource should not be reassigned
easiest if resource is final
easiest if resource must be assigned
and can be made implicitly final
try(Connection c = connection)
But since Java 8 we have effectively final!
This works in Java 9:
void doSomethingWith(Connection connection)
throws Exception {
try(connection) {
connection.doSomething();
}
}
compiler knows that connection
is not reassigned
developers know what effectively final means
Local-Variable Type Inference ⑩ |
Private Interface Methods ⑨ |
Try-With-Resources ⑨ |
Diamond Operator ⑨ |
SafeVarargs ⑨ |
Deprecation Warnings ⑨ |
A little more type inference.
Maybe the best example:
List<String> strings = new ArrayList<>();
used at a constructor call
tells Java to infer the parametric type
Diamond did not work with anonymous classes:
<T> Box<T> createBox(T content) {
// we have to put the `T` here :(
return new Box<T>(content) { };
}
Reason are non-denotable types:
might be inferred by compiler
for anonymous classes
can not be expressed by JVM
Java 9 infers denotable types:
<T> Box<T> createBox(T content) {
return new Box<>(content) { };
}
Gives compile error if type is non-denotable:
Box<?> createCrazyBox(Object content) {
List<?> innerList = Arrays.asList(content);
// compile error
return new Box<>(innerList) { };
}
Local-Variable Type Inference ⑩ |
Private Interface Methods ⑨ |
Try-With-Resources ⑨ |
Diamond Operator ⑨ |
SafeVarargs ⑨ |
Deprecation Warnings ⑨ |
One less warning you couldn’t do anything about.
Innocent looking code…
private <T> Optional<T> firstNonNull(T... args) {
return stream(args)
.filter(Objects::nonNull)
.findFirst();
}
Compiler warns (on call site, too):
Possible heap pollution from
parameterized vararg type
For generic varargs argument T… args
,
you must not depend on it being a T[]
!
private <T> T[] replaceTwoNulls(
T value, T first, T second) {
return replaceAllNulls(value, first, second);
}
private <T> T[] replaceAllNulls(T value, T... args) {
// loop over `args`, replacing `null` with `value`
return args;
}
Compiler is aware of the problem and warns you.
If you think, everything’s under control:
@SafeVarargs
private <T> Optional<T> firstNonNull(T... args) {
return // [...]
}
Or not… In Java 8 this is a compile error!
Invalid SafeVarargs annotation. Instance
method <T>firstNonNull(T...) is not final.
The @SafeVarargs
annotation:
tells the caller that all is fine
only makes sense on methods
that can not be overriden
Which methods can’t be overriden?
⇝ final
methods
What about private
methods?
⇝ Damn! 😭
Looong story, here’s the point:
In Java 9 @SafeVarargs
can be applied to private methods.
Local-Variable Type Inference ⑩ |
Private Interface Methods ⑨ |
Try-With-Resources ⑨ |
Diamond Operator ⑨ |
SafeVarargs ⑨ |
Deprecation Warnings ⑨ |
Another warning you couldn’t do anything about.
Should this code emit a warning?
// LineNumberInputStream is deprecated
import java.io.LineNumberInputStream;
public class DeprecatedImports {
LineNumberInputStream stream;
}
// LineNumberInputStream is deprecated
import java.io.LineNumberInputStream;
@Deprecated
public class DeprecatedImports {
LineNumberInputStream stream;
}
Java 9 no longer emits warnings
for importing deprecated members.
Warning free:
import java.io.LineNumberInputStream;
@Deprecated
public class DeprecatedImports {
LineNumberInputStream stream;
}
String Improvements ⑪ |
Collection Factories ⑨⑩ |
Stream Improvements ⑨⑩ |
Optional Improvements ⑨⑩⑪ |
Reactive Streams ⑨ |
Reactive HTTP/2 Client ⑪ |
Stack-Walking ⑨ |
OS Process Improvements ⑨ |
Some APIs are new,
many existing ones were improved.
String Improvements ⑪ |
Collection Factories ⑨⑩ |
Stream Improvements ⑨⑩ |
Optional Improvements ⑨⑩⑪ |
Reactive Streams ⑨ |
Reactive HTTP/2 Client ⑪ |
Stack-Walking ⑨ |
OS Process Improvements ⑨ |
Small improvements to String
.
Getting rid of white space:
String strip();
String stripLeading();
String stripTrailing();
Only at beginning and end of string:
" foo bar ".strip().equals("foo bar");
Wait, what about trim()
?
trim()
defines white space as:
any character whose codepoint
is less than or equal to'U+0020'
(the space character)
strip()
relies on Character::isWhitespace
,
which covers many more cases
Is a string only white space?
boolean isBlank();
Functionally equivalent to:
string.isBlank() == string.strip().isEmpty();
As soon as Java APIs get new method,
scour StackOverflow for easy karma!
Formerly accepted answer:
😍
Ta-da!
Processing a string’s lines:
Stream<String> lines();
splits a string on "\n"
, "\r"
, "\r\n"
lines do not include terminator
more performant than split("\R")
lazy!
String Improvements ⑪ |
Collection Factories ⑨⑩ |
Stream Improvements ⑨⑩ |
Optional Improvements ⑨⑩⑪ |
Reactive Streams ⑨ |
Reactive HTTP/2 Client ⑪ |
Stack-Walking ⑨ |
OS Process Improvements ⑨ |
Easy creation of ad-hoc collections.
Wouldn’t this be awesome?
List<String> list = [ "a", "b", "c" ];
Map<String, Integer> map = [ "one" = 1, "two" = 2 ];
Not gonna happen!
language change is costly
binds language to collection framework
strongly favors specific collections
List<String> list = List.of("a", "b", "c");
Map<String, Integer> mapImmediate = Map.of(
"one", 1,
"two", 2,
"three", 3);
Map<String, Integer> mapEntries = Map.ofEntries(
entry("one", 1),
entry("two", 2),
entry("three", 3));
collections are immutable
(no immutability in type system, though)
collections are value-based
null
elements/keys/values are forbidden
iteration order is random between JVM starts
(except for lists, of course!)
Creating immutable copies:
/* on List */ List<E> copyOf(Collection<E> coll);
/* on Set */ Set<E> copyOf(Collection<E> coll);
/* on Map */ Map<K, V> copyOf(Map<K,V> map);
Great for defensive copies:
public Customer(List<Order> orders) {
this.orders = List.copyOf(orders);
}
String Improvements ⑪ |
Collection Factories ⑨⑩ |
Stream Improvements ⑨⑩ |
Optional Improvements ⑨⑩⑪ |
Reactive Streams ⑨ |
Reactive HTTP/2 Client ⑪ |
Stack-Walking ⑨ |
OS Process Improvements ⑨ |
Small improvements to Java 8 streams.
Create a stream of zero or one elements:
long zero = Stream.ofNullable(null).count();
long one = Stream.ofNullable("42").count();
To use for
even less…
iterate(
T seed,
Predicate<T> hasNext,
UnaryOperator<T> next);
Example:
Stream
.iterate(1, i -> i<=10, i -> 2*i)
.forEach(System.out::println);
// output: 1 2 4 8
Counter Example:
Enumeration<Integer> en = // ...
Stream.iterate(
en.nextElement(),
el -> en.hasMoreElements(),
el -> en.nextElement())
.forEach(System.out::println);
first nextElement()
then hasMoreElements()
⇝ fail
Stream as long as a condition is true:
Stream<T> takeWhile(Predicate<T> predicate);
Example:
Stream.of("a-", "b-", "c-", "", "e-")
.takeWhile(s -> !s.isEmpty());
.forEach(System.out::print);
// output: a-b-c-
Ignore as long as a condition is true:
Stream<T> dropWhile(Predicate<T> predicate);
Example:
Stream.of("a-", "b-", "c-", "de-", "f-")
.dropWhile(s -> s.length() <= 2);
.forEach(System.out::print);
// output: de-f-
Create unmodifiable collections
(in the sense of List::of
et al)
with Collectors
:
Collector<T, ?, List<T>> toUnmodifiableList();
Collector<T, ?, Set<T>> toUnmodifiableSet();
Collector<T, ?, Map<K,U>> toUnmodifiableMap(
Function<T, K> keyMapper,
Function<T, U> valueMapper);
// plus overload with merge function
String Improvements ⑪ |
Collection Factories ⑨⑩ |
Stream Improvements ⑨⑩ |
Optional Improvements ⑨⑩⑪ |
Reactive Streams ⑨ |
Reactive HTTP/2 Client ⑪ |
Stack-Walking ⑨ |
OS Process Improvements ⑨ |
Small improvements to Java 8 Optional
.
No more !foo.isPresent()
:
boolean isEmpty()
Does exactly what
you think it does.
Optional::get
invites misuse
by calling it reflexively.
Maybe get
wasn’t the best name?
New:
T orElseThrow()
Works exactly as get
,
but more self-documenting.
Name in line with other accessors:
T orElse(T other)
T orElseGet(Supplier<T> supplier)
T orElseThrow()
throws NoSuchElementException
T orElseThrow(
Supplier<X> exceptionSupplier)
throws X
JDK-8160606
will deprecate
Optional::get
.
when?
for removal?
We’ll see…
Choose a non-empty Optional
:
Optional<T> or(Supplier<Optional<T>> supplier);
public interface Search {
Optional<Customer> inMemory(String id);
Optional<Customer> onDisk(String id);
Optional<Customer> remotely(String id);
default Optional<Customer> anywhere(String id) {
return inMemory(id)
.or(() -> onDisk(id))
.or(() -> remotely(id));
}
}
Like ifPresent
but do something if empty:
void ifPresentOrElse(
Consumer<T> action,
Runnable emptyAction);
Example:
void logLogin(String id) {
findCustomer(id)
.ifPresentOrElse(
this::logCustomerLogin,
() -> logUnknownLogin(id));
}
Turns an Optional
into a Stream
of zero or one elements:
Stream<T> stream();
private Optional<Customer> findCustomer(String id) {
// ...
}
Stream<Customer> findCustomers(List<String> ids) {
return ids.stream()
.map(this::findCustomer)
// now we have a Stream<Optional<Customer>>
.filter(Optional::isPresent)
.map(Optional::get)
}
private Optional<Customer> findCustomer(String id) {
// ...
}
Stream<Customer> findCustomers(List<String> ids) {
return ids.stream()
.map(this::findCustomer)
// now we have a Stream<Optional<Customer>>
// we can now filter-map in one step
.flatMap(Optional::stream)
}
List<Order> getOrders(Customer c)
is expensive:
List<Order> findOrdersForCustomer(String id) {
return findCustomer(id)
.map(this::getOrders) // eager
.orElse(new ArrayList<>());
}
Stream<Order> findOrdersForCustomer(String id) {
return findCustomer(id)
.stream()
.map(this::getOrders) // lazy
.flatMap(List::stream);
}
String Improvements ⑪ |
Collection Factories ⑨⑩ |
Stream Improvements ⑨⑩ |
Optional Improvements ⑨⑩⑪ |
Reactive Streams ⑨ |
Reactive HTTP/2 Client ⑪ |
Stack-Walking ⑨ |
OS Process Improvements ⑨ |
The JDK as common ground
for reactive stream libraries.
Publisher
produces items to consume
can be subscribed to
Subscriber
subscribes to publisher
onNext
, onError
, onComplete
Subscription
connection between publisher and subscriber
request
, cancel
create Publisher pub
and Subscriber sub
call pub.subscribe(sub)
pub creates Subscription script
and calls sub.onSubscription(script)
sub
can store script
sub
calls script.request(10)
pub
calls sub.onNext(element)
(max 10x)
pub
may call sub.OnError(err)
or sub.onComplete()
sub
may call script.cancel()
JDK only provides three interfaces
and one simple implementation.
(Also called Flow API.)
No JDK API uses them.
(No reactive HTTP connections etc.)
String Improvements ⑪ |
Collection Factories ⑨⑩ |
Stream Improvements ⑨⑩ |
Optional Improvements ⑨⑩⑪ |
Reactive Streams ⑨ |
Reactive HTTP/2 Client ⑪ |
Stack-Walking ⑨ |
OS Process Improvements ⑨ |
HTTP/2! And reactive! Woot!
To send a request and get a response:
use builder to create immutable HttpClient
use builder to create immutable HttpRequest
pass request to client to receive HttpResponse
HttpClient client = HttpClient.newBuilder()
.version(HTTP_2)
.connectTimeout(ofSeconds(5))
.followRedirects(ALWAYS)
.build();
More options:
proxy
SSL context/parameters
authenticator
cookie handler
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create("http://codefx.org"))
.setHeader("header-name", "header-value")
.build();
more HTTP methods (duh!)
individual timeout
individual HTTP version
request "100 CONTINUE"
before sending body
// the generic `String`...
HttpResponse<String> response = client.send(
request,
// ... comes from this body handler ...
BodyHandlers.ofString());
// ... and defines `body()`s return type
String body = response.body();
status code, headers, SSL session
request
intermediate responses
(redirection, authentication)
Great, but where’s the reactive sauce?
Three places:
send request asynchronously
provide request body with
Publisher<ByteBuffer>
receive response body with
Subscriber<String>
or
Subscriber<List<ByteBuffer>>
Submit request to thread pool until completes:
CompletableFuture<String> responseBody = client
.sendAsync(request, BodyHandlers.ofString())
.thenApply(this::logHeaders)
.thenApply(HttpResponse::body);
uses "a default executor" to field requests
pool can be defined when client is built with
HttpClient.Builder.executor(Executor)
If a request has a long body,
no need to prepare it in its entirety:
Publisher<ByteBuffer> body = // ...
HttpRequest post = HttpRequest.newBuilder()
.POST(BodyPublishers.fromPublisher(body))
.build();
client.send(post, BodyHandlers.ofString())
client
subscribes to body
as body
publishes byte buffers,
client
sends them over the wire
If a response has a long body,
no need to wait before processing:
Subscriber<String> body = // ...
HttpResponse<Void> response = client.send(
request,
BodyHandlers.fromLineSubscriber(subscriber));
client
subscribes body
to itself
as client
receives response bytes,
it parses to lines and passes to body
Benefits of reactive
request/response bodies:
receiver applies backpressure:
on requests, client
on responses, body
body
controls memory usage
early errors lead to partial processing
need "reactive tools" to create body
from higher-level Java objects (e.g. File
)
Short version:
there’s a class WebSocket
send[Text|Binary|…]
methods
return CompletableFuture
socket calls Listener
methods
on[Text|Binary|…]
(WebSocket
and Listener
behave like
Subscription
and Subscriber
.)
No long version. 😛
String Improvements ⑪ |
Collection Factories ⑨⑩ |
Stream Improvements ⑨⑩ |
Optional Improvements ⑨⑩⑪ |
Reactive Streams ⑨ |
Reactive HTTP/2 Client ⑪ |
Stack-Walking ⑨ |
OS Process Improvements ⑨ |
Examining the stack faster and easier.
StackWalker::forEach
void forEach (Consumer<StackFrame>);
public static void main(String[] args) { one(); }
static void one() { two(); }
static void two() {
StackWalker.getInstance()
.forEach(System.out::println);
}
// output
StackWalkingExample.two(StackWalking.java:14)
StackWalkingExample.one(StackWalking.java:11)
StackWalkingExample.main(StackWalking.java:10)
StackWalker::walk
T walk (Function<Stream<StackFrame>, T>);
static void three() {
String line = StackWalker.getInstance().walk(
frames -> frames
.filter(f -> f.getMethodName().contains("one"))
.findFirst()
.map(f -> "Line " + f.getLineNumber())
.orElse("Unknown line");
);
System.out.println(line);
}
// output
Line 11
getInstance
takes options as arguments:
SHOW_REFLECT_FRAMES
for reflection frames
SHOW_HIDDEN_FRAMES
e.g. for lambda frames
RETAIN_CLASS_REFERENCE
for Class<?>
forEach
and walk
operate on StackFrame
:
class and method name
class as Class<?>
bytecode index and isNative
Can upgrade to StackTraceElement
(expensive):
file name and line number
creating StackTraceElement
is expensive
(for file name and line number)
lazy evaluation pays off for partial traversal
(Benchmarks performed by Arnaud Roger)
String Improvements ⑪ |
Collection Factories ⑨⑩ |
Stream Improvements ⑨⑩ |
Optional Improvements ⑨⑩⑪ |
Reactive Streams ⑨ |
Reactive HTTP/2 Client ⑪ |
Stack-Walking ⑨ |
OS Process Improvements ⑨ |
Improving interaction with OS processes.
ls /home/nipa/tmp | grep pdf
Path dir = Paths.get("/home/nipa/tmp");
ProcessBuilder ls = new ProcessBuilder()
.command("ls")
.directory(dir.toFile());
ProcessBuilder grepPdf = new ProcessBuilder()
.command("grep", "pdf")
.redirectOutput(Redirect.INHERIT);
List<Process> lsThenGrep = ProcessBuilder
.startPipeline(List.of(ls, grepPdf));
Process
Cool new methods on Process
:
boolean supportsNormalTermination();
long pid();
CompletableFuture<Process> onExit();
Stream<ProcessHandle> children();
Stream<ProcessHandle> descendants();
ProcessHandle toHandle();
ProcessHandle
New functionality actually comes from ProcessHandle
.
Interesting static
methods:
Stream<ProcessHandle> allProcesses();
Optional<ProcessHandle> of(long pid);
ProcessHandle current();
ProcessHandle
can return Info
:
command, arguments
start time
CPU time
In Java 9:
Many lower-level APIs.
In Java 10 & 11:
Path.of(String); // ~ Paths.get(String)
Files.readString(Path);
Files.writeString(Path, CharSequence, ...);
Reader.transferTo(Writer);
ByteArrayInputStream.transferTo(OutputStream);
Reader.nullReader();
Writer.nullWriter();
InputStream.nullInputStream();
OutputStream.nullOutputStream();
In Java 10:
DateTimeFormatter.localizedBy(Locale locale);
In Java 11:
Collection.toArray(IntFunction<T[]> generator);
Predicate.not(Predicate<T> target); // static
Pattern.asMatchPredicate(); // ⇝ Predicate<String>
Launch Source File ⑪ |
Multi-Release JARs ⑨ |
Redirected Platform Logging ⑨ |
Launch Source File ⑪ |
Multi-Release JARs ⑨ |
Redirected Platform Logging ⑨ |
Faster feedback with fewer tools.
Compiling and running
simple Java programs is verbose.
Not any more!
java HelloJava11.java
How it works:
compiles source into memory
runs from there
Details:
requires module jdk.compiler (not in JRE)
processes options like class/module path et al.
interprets @files
for easier option management
Mostly similar to jshell
:
easier demonstrations
more portable examples
experimentation with new language features
(combine with --enable-preview
)
But also: script files!
Steps towards easier scripting:
arbitrary file names
shebang support
Use --source
if file doesn’t end in .java
:
java --source 11 hello-java-11
To create "proper scripts":
include shebang in source:
#!/opt/jdk-11/bin/java --source 11
name script and make it executable
execute it as any other script:
# from current directory:
./hello-java-11
# from PATH:
hello-java-11
Launch Source File ⑪ |
Multi-Release JARs ⑨ |
Redirected Platform Logging ⑨ |
"Do this on Java X, do that on Java Y."
Main
calls Version
:
public class Main {
public static void main(String[] args) {
System.out.println(new Version().get());
}
}
Version
exists twice:
public class Version {
public String get() { return "Java 8"; }
}
public class Version {
public String get() { return "Java 9"; }
}
(Btw, IDEs hate this!)
Now, here’s the magic:
compile Main
and Version[8]
to out/java-8
compile Version[9]
to out/java-9
use new jar
flag --release
:
jar --create --file out/mr.jar
-C out/java-8 .
--release 9 -C out/java-9 .
└ org
└ codefx ... (moar folders)
├ Main.class
└ Version.class
└ META-INF
└ versions
└ 9
└ org
└ codefx ... (moar folders)
└ Version.class
With java -cp out/mr.jar …Main
:
prints "Java 8"
on Java 8
prints "Java 9"
on Java 9
Great Success!
Launch Source File ⑪ |
Multi-Release JARs ⑨ |
Redirected Platform Logging ⑨ |
Use your logging framework of choice
as backend for JDK logging.
New logging infrastructure inside the JDK:
new interface System.Logger
used by JDK classes
instances created by System.LoggerFinder
The interesting bit:
LoggerFinder
is a service!
Logger
public class SystemOutLogger implements Logger {
public String getName() { return "SystemOut"; }
public boolean isLoggable(Level level) { return true; }
public void log(
Level level, ResourceBundle bundle,
String format, Object... params) {
System.out.println(/* ...*/);
}
// another, similar `log` method
}
LoggerFinder
public class SystemOutLoggerFinder
extends LoggerFinder {
public Logger getLogger(
String name, Module module) {
return new SystemOutLogger();
}
}
Module descriptor for system.out.logger:
module system.out.logger {
provides java.lang.System.LoggerFinder
with system.out.logger.SystemOutLoggerFinder;
}
Module system and JDK take care of the rest!
In Java 9:
In Java 10:
alternative memory device support (JEP 316)
In Java 11:
Application Class-Data Sharing⑩ |
Compact Strings ⑨ |
Indified String Concatenation ⑨ |
Application Class-Data Sharing⑩ |
Compact Strings ⑨ |
Indified String Concatenation ⑨ |
Improving application launch times.
JVM steps to execute a class’s bytecode:
looks up class in JAR
loads bytecode
verifies bytecode
stores class-data in
internal data structure
This takes quite some time.
If classes don’t change, the resulting
class-data is always the same!
Idea behind class-data sharing:
create class-data once
dump it into an archive
reuse the archive in future launches
(file is mapped into memory)
My experiments with a large desktop app:
archive has 250 MB for ~24k classes
launch time reduced from 15s to 12s
Bonus: Archive can be shared across JVMs.
Two variants:
Just for JDK classes
(⑫ includes archive - JEP 341)
JDK + application classes
We will focus on AppCDS.
Create a list of classes
$ java
-XX:+UseAppCDS
-XX:DumpLoadedClassList=classes.lst
# class path for dependencies
-jar app.jar
Then, classes.lst
contains
slash-separated names of loaded classes.
Use the list to create the archive:
$ java
-XX:+UseAppCDS
-Xshare:dump
-XX:SharedClassListFile=classes.lst
-XX:SharedArchiveFile=app-cds.jsa
--class-path app.jar # plus dependencies
Creates archive app-cds.jsa
.
Use the archive:
$ java
-XX:+UseAppCDS
-Xshare:on
-XX:SharedArchiveFile=app-cds.jsa
# class path for dependencies
-jar app.jar
For more, read this article:
tiny.cc/app-cds
observe sharing with -Xlog:class+load
(unified logging)
no wildcards or exploded JARs on class path
archive invalidation
module path not yet supported
Application Class-Data Sharing⑩ |
Compact Strings ⑨ |
Indified String Concatenation ⑨ |
Going from UTF-16 to ISO-8859-1.
20% - 30% of heap are char[]
for String
a char
is UTF-16 code unit ⇝ 2 bytes
most strings only require ISO-8859-1 ⇝ 1 byte
10% - 15% of memory is wasted!
For Java 9, String
was changed:
uses byte[]
instead of char[]
bytes per character:
1 if all characters are ISO-8859-1
2 otherwise
Only possible because String
makes
defensive copies of all arguments.
Simple benchmark:
(by Aleksey Shipilëv)
String method = generateString(size);
public String work() {
return "Calling method \"" + method + "\"";
}
Depending on circumstances:
throughput 1.4x
garbage less 1.85x
Application Class-Data Sharing⑩ |
Compact Strings ⑨ |
Indified String Concatenation ⑨ |
"Improving" + "String" + "Concatenation"
What happens when you run:
String s = greeting + ", " + place + "!";
bytecode uses StringBuilder
JIT may (!) recognize and optimize
by writing content directly to new byte[]
breaks down quickly
(e.g. with long
or double
)
new optimizations create new bytecode
new optimizations require recompile
test matrix JVMs vs bytecodes explodes
String::concat
?There is no such method.
concat(String… args)
requires toString
concat(Object… args)
requires boxing
Nothing fancy can be done
because compiler must use public API.
Invokedynamic came in Java 7:
compiler creates a recipe
runtime has to process it
defers decisions from compiler to runtime
(Used for lambda expressions and in Nashorn.)
With Indy compiler can express
"concat these things"
(without boxing!)
JVM executes by writing content
directly to new byte[]
.
Depending on circumstances:
throughput 2.6x
garbage less 3.4x
(Benchmarks by Aleksey Shipilëv)
Depending on circumstances:
throughput 2.9x
garbage less 6.4x
(Benchmarks by Aleksey Shipilëv)
In Java 9:
In Java 10:
In Java 11:
If you want the full picture:
Code First Java 9 Tutorial
https://blog.codefx.org/java/java-9-tutorial
⇜ Get my book!
You can hire me:
training (Java 8-11, JUnit 5)
consulting (Java 8-11)