Changes in Java 9 - ashishranjandev/developer-wiki GitHub Wiki
Major changes
- Language
- Compiler
- JVM
- Tooling
Module System - Modularluse the JDK - Why?
- Make JDK maintainable again
- Modularize applications
- Optional
What was issue?
rt.jar i.e. Runtime JAR -> 10,000 classes entangled
graph TD
JavaPrefs --> java.xml
java.xml --> java.base
java.sql --> java.xml
java.logging --> java.base
Cyclic Dependency is not supported.
Advantages:
- Increased Security
- Reduced footprint
- Easy deprecation
- Future Proof
- easy to skip
- Easy to experiment like jdf.incubate.httpclient in Java 9
What is module?
- name, self contained, groups related tool
- high level grouping construct
- has everything needed to execute
- Are jar files modules? No. Because there are no clearly defined boundaries.
Module JDK - Encapsulation
Module Name. | java.base |
public exposed.| java.logs |
public exposed.| java.util |
-------------------------
private, encapsulated | |
(not accessible outside). | |
-------------------------
module java.base {
exports java.lang;
exports java.utils;
---------> no requires, means there are no dependencies <br/>
}
module java.sql {
exports javax.sql;
exports javax.transaction.xa;
requires java.logging;
requires java.xml;
}
export takes package name
requires take module name
java --list-modules
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
java --describe-module java.sql
exports java.sql
exports javax.sql
requires java.base mandated
requires java.logging transitive
requires java.xml transitive
requires java.transaction.xa transitive
uses java.sql.Driver
javac -cp $classPath
java -cp $classPath
No issue unless
- Use Encapsulated types
- Using non-default modules
Secenarios
- Compiles with Java 8 with warning
- Runs in Java 9
- Compilation fails in Java 9 -> Use Jdeps for suggesting replacement
- Run with Java 9 also fails with --illegal-access=deny -> This will fail
- To run use java --illegal-access=permit -> Shows warning
- javac --add-exports java.base/sun.security.X509=ALL-UNNAMED
- jdeps -jdkinternals Main.class
import javax.xml.bind.DatatypeConverter -> Java 9 Compile -> package does not exist
Default: Only modules reachable from java.se accessible.
Why java.se.ee not included?
Most java application servers ship with their own implementations of the ee APIs. So to avoid confict.
javac --add-modules java.xml.bind
java --add-modules java.xml.bind
Main class loadded as part of unnamed module.
Use jdeps >> jdeps Main.class
Creating a list with 3 elements
Java 8 way
List<String> books = new ArrayList<>();
books.add("Java Programming");
books.add("Data Structures and Algorithms");
books.add("Design Patterns");
// Alterative
List<String> books = Arrays.asList("Java Programming", "Data Structures and Algorithms", "Design Patterns");
// Anonymous Instance collection Type
//
new ArrayList<>() {{ add("Java 9 mad"); }}
Collections.emptyList();
Java 9 way
List.of(1, 2, 3);
// Intermediate Array Allocation
Set.of("first", "second");
// Null not allowed
// Duplicates not allowed
REPL for Java -> Read Eval Print logs -> like Lisp, Groovy, Scala
Why?
- Quickly test Ideas
- Exploring APIs -> Code completion, built in doc
- Teaching tool
$ jshell
jshell> 1 + 1
$1 ==> 2
// No semicolon is required
// What is $1 -> Identifier that can be used later.
jshell> "abc".matches("[ab]+b\\s")
jshell> /save mysession.jsh
jshell> /open mysession.jsh. -> print will happen again
jshell --class-path -> Sets class path
jshell --class-path commons-lang-3.5.jar. -> explore libraries
Jshell jshell = jshell.create();
List<SnippetEvent> events = jshell.eval("int i = 0;")
Stream<SnippetEvent> vars = jshell.variables();
Map.of("Key 1", 1, "Key 2", 2) -> {Key 1= 1, Key 2 = 2}
Map.ofEntries(Map.entry("key 1", true), Map.entry("key2", false))
// Iteration Order is not guranteed for Map.Sets
They are ephimermal. It can have infinite elements.
Improvements
- takewhile
- dropwhile
- ofNullable() -> For 0 or 1
- iterate(T seed, Predicate<> hasNext, Unary next). -> Predicate has been added.
Sample Stream - 0,0,0,1,1,2,2,2
- takeWhile(is 0) -> 0,0,0. will be taken.
- dropWhile(is 0) -> 1,1,2,1,2 will be taken.
- Works well for Ordered Streams
New Collectors -> Collectors -> Materialise stream into new collection
Collector<...> filtering(Predicate<..> predicate, Collector<..> downstream)
book.collect(groupingBy(Book::getAuthors, filtering(b -> b.getPrice() > 10, toSet())));
Existing
Optional.of("Hi")
Optional.empty()
s.map(String::toUpperCase) // "Hi"
i.map(n -> n + 1) -> Empty option, no NPE
Addition to Optionals
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction);
public Stream<T> stream();
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier);
// Before ifPresentOrElse
full.ifPresent(System.out::println);
if (full.isPresent()) {
System.out.println(full.get());
} else {
System.out.println("Nothing here");
}
// After Java 9
full.ifPresentOrElse(System.out::println,
() -> System.out.println("Nothing here!"));
Interoperability between optional and streams. Now we can get a stream of 1 and 2.
Stream<Optional<Integer>> optionals =
Stream.of(Optional.of(1), Optional.empty(), Optional.of(2));
Stream<Integer> ints = optionals.flatMap(Optional::stream);
ints.forEach(System.out::println);
// Find all first authors of the books
Set<String> authors = Book.getBooks()
.map(book -> book.authors.stream().findFirst())
.flatMap(optAuthor -> optAuthor.stream())
.collect(Collectors.toSet());
System.out.println(authors);
Chaining of Optional
// Java 8
Book bestBookBefore = getBestOffer()
.orElse(getExternalOffer().orElse(localFallback.get())); // .get() is BAD!
Optional<Book> bestBook =
getBestOffer()
.or(() -> getExternalOffer())
.or(() -> localFallback);
Before java 9 only the resources declared at the start would get closed. However, If the resource is declared before the start it would not get closed
// Java 8
public void normalTryWithResources() throws IOException {
try (FileInputStream fis = new FileInputStream("~/tmp/test")) {
fis.read();
}
}
// fis = null; // Re-assignment makes fis not 'effectively final'
try (FileInputStream fis2 = fis) {
fis2.read();
}
// Java 9
public void doWithFile(FileInputStream fis) throws IOException {
// Only if fis is 'effectively final', can this form be used
// fis has to be effectively final
try (fis) {
fis.read();
}
}
ArrayList<String> list1 = new ArrayList<String>();
ArrayList<String> list2 = new ArrayList<>();
ArrayList<String> list3 = new ArrayList<>() {
@Override
public boolean add(String s) {
System.out.println("Adding " + s);
return super.add(s);
}
};
For common logic in Interfaces
double getPrice();
default double getPriceWithTax() {
return getPriceWithTaxInternal();
}
default double getPriceWithDiscount() {
return getPriceWithTaxInternal() * 0.80;
}
private double getPriceWithTaxInternal() {
return getPrice() * 1.20;
}
- Java Doc is HTML 5 Compliant
- Search added in Java Docs
- Modules included in Java Docs
- Support of Unicode upgraded from 6.2 to 8.0
- Properties file now supports UTF-8 and not ISO-8859-1 -> No effect if only ASCII characters are there.
- For Locale it uses Common Locale Data Repository(public data set describing localisation formats). Previously Java had its own home brew formats. -> For no impact, in java.locale.providers put COMPAT in front of CLDR
Added in Time
- long dividedBy(Duration divisor)
- Duration truncatedTo(TemporalUnit unit)
- static Clock systemUTC()
Added in Clock
- static Clock systemUTC()
LocalDate birthDay = LocalDate.of(1991, 12, 6);
System.out.println(birthDay.
datesUntil(LocalDate.now(), Period.ofYears(1)).
map(date -> Year.of(date.getYear())).
filter(Year::isLeap).
count());
- java.lang.Process - Represents all the process created by Java
- java.lang.ProcessHandler - Represent any native process on the operating system
classDiagram
class Process {
+start(): ProcessHandle
+destroy(): void
+isAlive(): boolean
+toHandle(): ProcessHandle
}
class ProcessHandle {
+info(): ProcessHandleInfo
+onExit(): CompletableFuture<ProcessHandle>
+destroy(): void
+info(): ProcessHandleInfo
}
class ProcessHandleInfo {
+pid(): long
+command(): String
+arguments(): String[]
+user(): String
}
JavaProcess --> ProcessHandle
ProcessHandle --> ProcessHandleInfo
long pidOld = Long.parseLong(ManagementFactory.getRuntimeMXBean().getName().split("@")[0]);
// With the new ProcessHandle API in Java 9:
long pidNew = ProcessHandle.current().pid();
long parentPid = ProcessHandle.current().parent().get().pid();
ProcessHandle textEditHandle =
ProcessHandle.allProcesses()
.filter(h -> h.info().commandLine().map(cmd -> cmd.contains("TextEdit")).orElse(false))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("No matching handle found"));
HTTP 2 | HTTP 1 |
---|---|
Request/Response | Request/Response |
GET/PUT/POST | GET/PUT/POST |
Binary Protocol | Text Protocol |
Mandatory TLS | Optional TLS |
Multiplexing over same TCP Connection | No Multiplexing |
Server Push Capability | No Server Push Capability |
HttpClient.Builder builder = HttpClient.newBuilder();
builder.version(HttpClient.Version.HTTP_2)
.followRedirects(HttpClient.Redirect.ALWAYS);
HttpClient client = builder.build();
HttpRequest request = HttpRequest.newBuilder(URI.create("https://www.google.com"))
.header("User-Agent", "Java")
.timeout(Duration.ofMillis(500))
.GET()
.build();
CompletableFuture<HttpResponse<String>> response =
client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
response.thenAccept(r -> {
System.out.println("Version: " + r.version());
System.out.println(r.body());
});
response.join();
- BackPressure Support for Stream Data
- Vendor Neutral Specification
- java.util.Concurrent - Flow API - Only Interfaces
- Interoperability with Akka Streams, Rx Java 2, Spring 5
- Not an end user API
sequenceDiagram
participant Publisher as Publisher
participant Subscription as Subscription
participant Subscriber as Subscriber
Publisher ->> Subscription: subscribe
Subscription ->> Subscriber: onSubscribe
Subscriber ->> Subscription: request
Subscription ->> Subscriber: onNext
Subscriber ->> Subscription: onComplete
Subscriber ->> Subscription: onError
Subscription ->> Publisher: cancel
- Low Performance
- No Partial Handling
classDiagram
class StackWalker {
+walk(): Stream<StackWalker.StackFrame>
+getCallerClass(): Class<?>
}
class StackWalker.StackFrame {
-className: String
-methodName: String
}
class StackWalker.Option {
-SHOW_REFLECT_FRAMES: StackWalker.Option
-SHOW_HIDDEN_FRAMES: StackWalker.Option
}
StackWalker --> StackWalker.StackFrame
StackWalker --> StackWalker.Option
- CMS - Deprecated
- G1GC - Default GC in Server mode (Introduced in Java 6)
Generation garbage collectors divided the Heap
- Eden
- Survivor Region
- Tenured
Issues
- Stop the world GC Pause
- Too many configurations
What is new
- Instead of dividing in 3 continous regions - Heap divided into multiple regions of 32 MB
- Now G1 GC works incrementally -low pause time
- GC of Eden and Survivor region can happen in parallel
- parallel marking phase
- Tuneable pause time, low pause time
- (-) Parallel GC threads - takes up CPU time
- (-) Trade throughput for latency
- Automated tuning
- this is especially beneficial for large heaps
- It has pause time interval, so that GC does not happen very often
- Faster memory management with low tuning
-XX:MaxGCPauseMillis=200
## Not enabled by default
-XX:+G1EnableStringDeduplication
- Compact Strings
- Concatenation
Before Java 9
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
This char[] used to store - Ashish as |A|0|s|0|i|0|s|h|0|
Default encoding is UTF-16. Hence for (most) string with only ASCII values we were using double storage in heap.
From Java 9
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
@Stable
private final byte[] value;
/**
* The identifier of the encoding used to encode the bytes in
* {@code value}. The supported values in this implementation are
*
* LATIN1
* UTF16
*/
private final byte coder;
The code byte will tell us if value contains LATIN 1 or UTF 16. Hence for latin characters we can save space.
Since Strings are major part of any Heap. This can free up around 10-15% of heap.
String s = "a" + "b" + "c";
// this gets translated to
String s = new StringBuilder;
s.append("a");
s.append("b");
s.append("c");
However if we were to improve it, it would need recompiling.
- Hence Java has marked this as Invokedynamic byte code.
- There will be a late binding to actual implementation.
- Stable bytecode, where future improvement is possible. This was used for lambdas as well.
sequenceDiagram
participant Java as Java
participant Raw_Data as Raw_Data
Java ->> Raw_Data: ObjectOutputStream
Raw_Data ->> Java: ObjectInputStream
the second step is security nightmare as any input can become executable java code.
public interface ObjectInputFilter {
/**
* Check the class, array length, number of object references, depth,
* stream size, and other available filtering information.
* Implementations of this method check the contents of the object graph being created
* during deserialization. The filter returns {@link Status#ALLOWED Status.ALLOWED},
* {@link Status#REJECTED Status.REJECTED}, or {@link Status#UNDECIDED Status.UNDECIDED}.
*
* @param filterInfo provides information about the current object being deserialized,
* if any, and the status of the {@link ObjectInputStream}
* @return {@link Status#ALLOWED Status.ALLOWED} if accepted,
* {@link Status#REJECTED Status.REJECTED} if rejected,
* {@link Status#UNDECIDED Status.UNDECIDED} if undecided.
*/
Status checkInput(FilterInfo filterInfo);
}
checkInput is used before the bytes are actually used by using
- Instance Method - ObjectInputSteam::setObjectInputFilter
- All Streams - ObjectInputFilter.Config.setSerialFilter
- (Backported till Java 6)jdk.serialFilter - maxbytes,maxarrays,maxdepth or packages (allowed or blocked).
Now Application Layer protocol can be selected during TLS handshake.
Earlier ALP negotiation used to happen after the TLS handshake.
sequenceDiagram
participant Client as Client
participant Server as Server
Client ->> Server: Client Hello with Protocol
Server ->> Client: Server Hello with Selected protocol
This is required for HTTP/2.
TLS is designed for reliable communications like TCP. However DTLS is for unsecured comm. No order no acknowledgement.
Java 9 supports DTLS 1.0,1.2 -> along with TLS 1.1, 1.2 through SSL Engine and Datagram socket.
Even if the server certificate is valid. It could be revoked by authority. Hence the application has to check the server status with OCSP. This could overwhelm OCSP server.
sequenceDiagram
participant Client as Client
participant Server as Server
participant OCSP_Server as OCSP_Server
Client ->> Server: Start TLS Connection
Server ->> OCSP_Server : Verify Certificate Status - with timestamp till what time response can be cached
Server ->> Server : Cache the OCSP response
Server ->> Client: return stapled certificate - with timestamp till what time Cert can be trusted
An attack was found on this hash function that allows attackers to create deliberate hash collisions, that's bad. In response to this, certificates that are still signed using the SHA-1 hash function are now rejected by default in Java 9.
default rejection only applies to certificates that chain back to a root certificate that is part of the default trusted root certificate authorities in the JDK. If you use your own local or enterprise root certificates with the SHA-1 hash, those are not affected. They will still be accepted. Of course, you shouldn't use those, and if you do, you should move to a more secure certificate. The good news is that in Java 9 support is added for the SHA-3 family of hash functions, which are currently the state of the art in standardized secure hashing