Java 27 and 26: runnable demos and captured output for every JEP, plus version lanes
Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01B38FGKKam5SCGgwgduVAh3
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
build/
|
||||||
|
*.class
|
||||||
|
*.jfr
|
||||||
|
.lib/
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Ankur Mhatre
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# javademos
|
||||||
|
|
||||||
|
Runnable companion code for the ankurm.com post **"Java 27 Is Out: Every JEP, Plus the Java 26 Changes You Skipped"**, and for the Java version guides
|
||||||
|
(<https://ankurm.com/java-21-to-25-lts-features/>). Every number and transcript in the post comes from a file in `docs/output/`, produced by a script in `scripts/`.
|
||||||
|
|
||||||
|
## What was tested against
|
||||||
|
|
||||||
|
| Thing | Version | Note |
|
||||||
|
|---|---|---|
|
||||||
|
| JDK 25 (LTS) | Temurin 25.0.4.1+1 | the baseline for "broke on 26" |
|
||||||
|
| JDK 26 | Amazon Corretto 26.0.2.1 | GA 2026-03-17, non-LTS |
|
||||||
|
| JDK 27 | Amazon Corretto 27+35 | GA 2026-09-15, non-LTS |
|
||||||
|
| Containers | `amazoncorretto:26`, `amazoncorretto:27` | same vendor for both, so only the JDK version changes |
|
||||||
|
| JOL | 0.17 | downloaded and sha1-checked by `scripts/object-headers.sh` |
|
||||||
|
|
||||||
|
Next LTS: Java 29, September 2027 (Oracle Java SE Support Roadmap). Layout: `g1-container/`, `object-headers/`, `jep-tour/` (27 features and `broken/` 26-era sources),
|
||||||
|
`recap26/` (the Java 26 lane), `other-changes/`, `api-diff/`.
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export JDK25=/path/to/jdk-25 JDK26=/path/to/jdk-26 JDK27=/path/to/jdk-27 # defaults are in scripts/env.sh
|
||||||
|
./scripts/jep-tour.sh # the five 27 feature demos, self-asserting
|
||||||
|
./scripts/recap26.sh # the 26 recap lane
|
||||||
|
./scripts/run-all.sh # regenerate every docs/output/*.txt (about 15-20 minutes; needs Docker)
|
||||||
|
```
|
||||||
|
|
||||||
|
Timings, RSS, GC counts and the Vector species are machine dependent; treat them as shape, not results. Everything else is byte-stable.
|
||||||
|
|
||||||
|
## Chapters
|
||||||
|
|
||||||
|
| # | Chapter |
|
||||||
|
|---|---|
|
||||||
|
| 1 | [What shipped, and what to believe](docs/01-what-shipped.md) |
|
||||||
|
| 2 | [G1 becomes the default, even on one CPU (JEP 523)](docs/02-g1-default.md) |
|
||||||
|
| 3 | [Compact object headers (JEP 534)](docs/03-compact-headers.md) |
|
||||||
|
| 4 | [Post-quantum TLS (JEP 527)](docs/04-post-quantum-tls.md) |
|
||||||
|
| 5 | [Primitive patterns (JEP 532)](docs/05-primitive-patterns.md) |
|
||||||
|
| 6 | [Lazy constants (JEP 531)](docs/06-lazy-constants.md) |
|
||||||
|
| 7 | [Structured concurrency (JEP 533)](docs/07-structured-concurrency.md) |
|
||||||
|
| 8 | [PEM encodings (JEP 538)](docs/08-pem-api.md) |
|
||||||
|
| 9 | [JFR redaction (JEP 536) and the Vector API (JEP 537)](docs/09-jfr-redaction-and-vector.md) |
|
||||||
|
| 10 | [Other 27 changes](docs/10-other-27-changes.md) |
|
||||||
|
| 11 | [Recap: the Java 26 changes you skipped](docs/11-recap-26.md) |
|
||||||
|
| 12 | [Version lanes: 25 to 29](docs/12-lanes-25-to-29.md) |
|
||||||
|
| 13 | [Upgrade checklist](docs/13-upgrade-checklist.md) |
|
||||||
|
|
||||||
|
## Captured output (`docs/output/`)
|
||||||
|
|
||||||
|
| File | Produced by | What it is |
|
||||||
|
|---|---|---|
|
||||||
|
| `01`-`03` | `g1-default.sh` | which collector each JDK picks, by container size; the opt-out |
|
||||||
|
| `04`-`06` | `g1-cost.sh`, `g1-tuning.sh` | what G1 costs on one CPU, and two tested explanations |
|
||||||
|
| `10`-`15` | `object-headers.sh` | JOL instance sizes on 26, 27, 27 opted out; the JOL record failure and workaround |
|
||||||
|
| `20-broken-*` | `broken-on-27.sh` | javac's own messages for 26-era preview code on 27 |
|
||||||
|
| `21`-`25` | `jep-tour.sh` | the five 27 feature demos |
|
||||||
|
| `30`-`32` | `tls-pq.sh` | key-exchange matrix, ClientHello size, the opt-out |
|
||||||
|
| `40` | `jfr-redaction.sh` | five redaction configurations |
|
||||||
|
| `50`-`52` | `api-diff.sh` | public API diffs, 26 to 27, 25 to 26, Vector only |
|
||||||
|
| `60`-`62` | `other-changes.sh` | removed options, behaviour changes, new 27 API |
|
||||||
|
| `70`-`75` | `recap26.sh` | final-field mutation, AOT cache matrix and startup, removals, 26 API, HTTP/3 |
|
||||||
|
|
||||||
|
## Not reproduced
|
||||||
|
|
||||||
|
`jdk.java.net/27` and `openjdk.org/jeps` returned 403 while this was written; the JEP list was cross-checked against four secondary sources and the JDK binaries themselves.
|
||||||
|
HTTP/3 was not spoken to a real HTTP/3 server (no UDP egress). JEP 522 was not benchmarked. JDK-8377013 could not be reproduced.
|
||||||
|
|
||||||
|
MIT licensed, see `LICENSE`.
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Diff two javap dumps produced by dump_api.py and report changes to PUBLIC API only.
|
||||||
|
|
||||||
|
usage: apidiff.py <old-dump> <new-dump> [--vector-only]
|
||||||
|
|
||||||
|
A class is reported only if its declaration starts with 'public' in the old or the new dump, so
|
||||||
|
package-private implementation classes (which javap -public still lists) do not drown the signal.
|
||||||
|
"""
|
||||||
|
import collections
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def parse(path):
|
||||||
|
classes = collections.OrderedDict()
|
||||||
|
decl = {}
|
||||||
|
cur = None
|
||||||
|
for line in open(path):
|
||||||
|
line = line.rstrip("\n")
|
||||||
|
if line.startswith("#####") or line.startswith("Compiled from") or not line.strip():
|
||||||
|
continue
|
||||||
|
if not line.startswith(" "):
|
||||||
|
m = re.search(r"(?:class|interface|enum|@interface|record)\s+([\w.$]+)", line)
|
||||||
|
cur = m.group(1) if m else line
|
||||||
|
classes[cur] = set()
|
||||||
|
decl[cur] = line
|
||||||
|
elif cur is not None and line.strip() != "}":
|
||||||
|
classes[cur].add(line.strip())
|
||||||
|
return classes, decl
|
||||||
|
|
||||||
|
|
||||||
|
def is_public(decl_line):
|
||||||
|
return decl_line.startswith("public ") or decl_line.startswith("protected ")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
old, old_decl = parse(sys.argv[1])
|
||||||
|
new, new_decl = parse(sys.argv[2])
|
||||||
|
vector_only = "--vector-only" in sys.argv
|
||||||
|
|
||||||
|
def keep(name):
|
||||||
|
if vector_only and not name.startswith("jdk.incubator.vector."):
|
||||||
|
return False
|
||||||
|
if name.startswith("file") or "Error: Access Flags" in old_decl.get(name, "") + new_decl.get(name, ""):
|
||||||
|
return False
|
||||||
|
return is_public(old_decl.get(name, "")) or is_public(new_decl.get(name, ""))
|
||||||
|
|
||||||
|
print("## public classes removed")
|
||||||
|
for c in sorted(set(old) - set(new)):
|
||||||
|
if keep(c):
|
||||||
|
print(" " + c)
|
||||||
|
print("## public classes added")
|
||||||
|
for c in sorted(set(new) - set(old)):
|
||||||
|
if keep(c):
|
||||||
|
print(" " + c)
|
||||||
|
print("## public class changes")
|
||||||
|
for c in sorted(set(old) & set(new)):
|
||||||
|
if not keep(c):
|
||||||
|
continue
|
||||||
|
removed = old[c] - new[c]
|
||||||
|
added = new[c] - old[c]
|
||||||
|
d_old, d_new = old_decl[c], new_decl[c]
|
||||||
|
if removed or added or d_old != d_new:
|
||||||
|
print(c)
|
||||||
|
if d_old != d_new:
|
||||||
|
print(" - " + d_old.strip())
|
||||||
|
print(" + " + d_new.strip())
|
||||||
|
for x in sorted(removed):
|
||||||
|
print(" - " + x)
|
||||||
|
for x in sorted(added):
|
||||||
|
print(" + " + x)
|
||||||
|
|
||||||
|
|
||||||
|
main()
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Dump the public API signatures of every exported class in the JDK's java.se-ish modules.
|
||||||
|
usage: dump_api.py <JAVA_HOME> <out-file>"""
|
||||||
|
import subprocess, sys, re, os
|
||||||
|
home, out = sys.argv[1], sys.argv[2]
|
||||||
|
env = {k:v for k,v in os.environ.items() if k!='JAVA_TOOL_OPTIONS'}
|
||||||
|
jimage = subprocess.run([f'{home}/bin/jimage','list',f'{home}/lib/modules'],capture_output=True,text=True,env=env).stdout
|
||||||
|
mods = {}
|
||||||
|
cur=None
|
||||||
|
for line in jimage.splitlines():
|
||||||
|
if line.startswith('Module: '): cur=line.split(': ')[1].strip(); mods[cur]=[]
|
||||||
|
elif cur and line.strip().endswith('.class'):
|
||||||
|
mods[cur].append(line.strip()[:-6])
|
||||||
|
WANT = re.compile(r'^(java|jdk\.(jfr|httpserver|incubator\.vector|management|net|jshell|jartool|jlink|jpackage|javadoc|compiler|jcmd|unsupported|security\.auth)|javafx)')
|
||||||
|
res=[]
|
||||||
|
for m,classes in sorted(mods.items()):
|
||||||
|
if not WANT.match(m): continue
|
||||||
|
names=[]
|
||||||
|
for c in classes:
|
||||||
|
if c=='module-info': continue
|
||||||
|
pk=c.rsplit('/',1)[0].replace('/','.') if '/' in c else ''
|
||||||
|
if re.search(r'(^|\.)(internal|impl)(\.|$)|^sun\.|^com\.sun\.(?!net\.httpserver|management|security\.auth|source)',pk): continue
|
||||||
|
names.append(c.replace('/','.'))
|
||||||
|
if not names: continue
|
||||||
|
# javap handles binary names with $ for nested
|
||||||
|
names=[n for n in names if not re.search(r'\$\d',n)]
|
||||||
|
txt=''
|
||||||
|
for i in range(0,len(names),200):
|
||||||
|
p=subprocess.run([f'{home}/bin/javap','--module',m,'-public']+names[i:i+200],capture_output=True,text=True,env=env)
|
||||||
|
txt+=p.stdout
|
||||||
|
res.append(f'##### MODULE {m}\n'+txt)
|
||||||
|
open(out,'w').write('\n'.join(res))
|
||||||
|
print(out, sum(len(r) for r in res)//1024,'KB')
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# 1. What shipped, and what to believe
|
||||||
|
|
||||||
|
Next: [2. G1 becomes the default](02-g1-default.md)
|
||||||
|
|
||||||
|
Java 27 reached General Availability on 2026-09-15. It is **not** an LTS: Oracle's support roadmap lists Premier support for 27 until March 2027,
|
||||||
|
and names Java 29 (September 2027) as the next LTS after 25. Everything in this repository was run on real JDK binaries; where a claim comes from
|
||||||
|
prose rather than from a run, the chapter says so.
|
||||||
|
|
||||||
|
## The nine JEPs in 27
|
||||||
|
|
||||||
|
| JEP | Title | Status in 27 | Chapter |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 523 | Make G1 the Default Garbage Collector in All Environments | final | [2](02-g1-default.md) |
|
||||||
|
| 534 | Compact Object Headers by Default | final | [3](03-compact-headers.md) |
|
||||||
|
| 527 | Post-Quantum Hybrid Key Exchange for TLS 1.3 | final | [4](04-post-quantum-tls.md) |
|
||||||
|
| 532 | Primitive Types in Patterns, instanceof, and switch | fifth preview | [5](05-primitive-patterns.md) |
|
||||||
|
| 531 | Lazy Constants | third preview | [6](06-lazy-constants.md) |
|
||||||
|
| 533 | Structured Concurrency | seventh preview | [7](07-structured-concurrency.md) |
|
||||||
|
| 538 | PEM Encodings of Cryptographic Objects | third preview | [8](08-pem-api.md) |
|
||||||
|
| 536 | JFR: In-Process Data Redaction | final | [9](09-jfr-redaction-and-vector.md) |
|
||||||
|
| 537 | Vector API | twelfth incubator | [9](09-jfr-redaction-and-vector.md) |
|
||||||
|
|
||||||
|
## The ten JEPs in 26 (the release many teams skipped)
|
||||||
|
|
||||||
|
| JEP | Title | Status in 26 | Chapter |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 500 | Prepare to Make Final Mean Final | final | [11](11-recap-26.md) |
|
||||||
|
| 504 | Remove the Applet API | final | [11](11-recap-26.md) |
|
||||||
|
| 516 | Ahead-of-Time Object Caching with Any GC | final | [11](11-recap-26.md) |
|
||||||
|
| 517 | HTTP/3 for the HTTP Client API | final | [11](11-recap-26.md) |
|
||||||
|
| 522 | G1 GC: Improve Throughput by Reducing Synchronization | final | [11](11-recap-26.md) (not measured here) |
|
||||||
|
| 524 | PEM Encodings of Cryptographic Objects | second preview | [8](08-pem-api.md) |
|
||||||
|
| 525 | Structured Concurrency | sixth preview | [7](07-structured-concurrency.md) |
|
||||||
|
| 526 | Lazy Constants | second preview | [6](06-lazy-constants.md) |
|
||||||
|
| 529 | Vector API | eleventh incubator | [9](09-jfr-redaction-and-vector.md) |
|
||||||
|
| 530 | Primitive Types in Patterns, instanceof, and switch | fourth preview | [5](05-primitive-patterns.md) |
|
||||||
|
|
||||||
|
## How the list was verified
|
||||||
|
|
||||||
|
openjdk.org/jeps and jdk.java.net/27 returned HTTP 403 from the sandbox used to write this, and no workaround was attempted. The lists were
|
||||||
|
cross-checked between the Inside.java release announcement, Oracle's release blog, InfoWorld's and foojay's write-ups, and then against the
|
||||||
|
**binaries**: the JDK 27 and 26 builds used here were diffed class by class ([51](output/51-api-diff-26-to-27.txt),
|
||||||
|
[52](output/52-api-diff-25-to-26.txt)), which is how two API names in a secondary write-up were corrected. Treat the JEP titles above as
|
||||||
|
"as reported by four secondary sources"; treat every behaviour as "observed on Corretto 26.0.2.1 / 27+35".
|
||||||
|
|
||||||
|
## What was not reproduced
|
||||||
|
|
||||||
|
* JDK-8377013 (a reported `TimeZone.getDefault()` regression) could not be reproduced and is left out.
|
||||||
|
* HTTP/3 was never spoken to a real HTTP/3 server: the sandbox has no UDP egress ([11](11-recap-26.md)).
|
||||||
|
* JEP 522 (G1 synchronization) was not benchmarked.
|
||||||
|
|
||||||
|
Next: [2. G1 becomes the default](02-g1-default.md)
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# 2. G1 becomes the default, even on one CPU (JEP 523)
|
||||||
|
|
||||||
|
Prev: [1. What shipped](01-what-shipped.md) · Next: [3. Compact object headers](03-compact-headers.md)
|
||||||
|
|
||||||
|
## The old rule
|
||||||
|
|
||||||
|
Since JDK 9 the JVM has chosen its collector by "server-class machine" ergonomics: G1 if it sees at least **two** CPUs and roughly **1792 MB**
|
||||||
|
of memory, Serial otherwise. A container limited to one CPU, or to 1 GB, quietly got Serial. JEP 523 removes the exception: G1 is chosen
|
||||||
|
in every environment.
|
||||||
|
|
||||||
|
## What the containers say
|
||||||
|
|
||||||
|
[`scripts/g1-default.sh`](../scripts/g1-default.sh) runs [`GcReport`](../g1-container/src/GcReport.java) in `amazoncorretto:26` and `:27`
|
||||||
|
containers. Only the JDK version and the limits change.
|
||||||
|
|
||||||
|
| Container | JDK 26 picks | JDK 27 picks | Transcript |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `--cpus=1 --memory=2g` | Serial (`UseSerialGC ... ERGONOMIC`) | G1 | [01](output/01-g1-default-1cpu-vs-2cpu.txt) |
|
||||||
|
| `--cpus=2 --memory=2g` | G1 | G1 | [01](output/01-g1-default-1cpu-vs-2cpu.txt) |
|
||||||
|
| `--cpus=2 --memory=1g` | Serial | G1 | [02](output/02-g1-default-memory.txt) |
|
||||||
|
|
||||||
|
So both halves of the old rule are gone: CPU count *and* memory limit. An explicit `-XX:+UseSerialGC` still wins
|
||||||
|
([03](output/03-g1-default-optout.txt)); its origin shows as `VM_CREATION`, not `ERGONOMIC`.
|
||||||
|
|
||||||
|
## What it costs when your container is small
|
||||||
|
|
||||||
|
[`Workload`](../g1-container/src/Workload.java) allocates 50,000 short-lived objects per batch, keeps about 1% of them, and reports wall time, GC
|
||||||
|
count and time, the slowest single batch and peak RSS. Three runs per configuration, 1 CPU, 2 GB ([04](output/04-g1-cost-1cpu.txt)):
|
||||||
|
|
||||||
|
| 4000 batches | wall time | GC count / total GC time | slowest batch | peak RSS |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 26, default (Serial) | 6.0-6.4 s | 1275 / ~1.85 s | 115-142 ms | 557 MB |
|
||||||
|
| 27, default (G1) | 8.2-8.4 s | 140 / ~1.98 s | 35-41 ms | 547 MB |
|
||||||
|
| 27, `-XX:+UseSerialGC` | 5.0-5.7 s | 1264 / ~1.66 s | 119-128 ms | 557 MB |
|
||||||
|
|
||||||
|
The shape, not the digits: on one CPU, G1 has roughly **3x shorter worst-case stalls** and takes **about a third longer** to finish the same allocation-heavy work.
|
||||||
|
G1's concurrent work has to share the only CPU with your application.
|
||||||
|
|
||||||
|
A short-lived process makes it look worse ([05](output/05-g1-cost-1cpu-short.txt), 400 batches): 26 finishes in ~0.53 s using ~102 MB RSS; 27 with G1 takes
|
||||||
|
~1.0 s and touches ~492 MB, because G1 grows the heap toward the 512 MB default maximum instead of collecting early. Serial on 27 matches 26 (~0.52 s, ~98 MB).
|
||||||
|
|
||||||
|
## Two suspects, tested
|
||||||
|
|
||||||
|
[`scripts/g1-tuning.sh`](../scripts/g1-tuning.sh) ([06](output/06-g1-tuning-1cpu.txt)):
|
||||||
|
|
||||||
|
1. JDK 27 also changed the heap free ratios G1 uses (`MinHeapFreeRatio` 40 -> 0, `MaxHeapFreeRatio` 70 -> 100). Restoring 40/70 changed nothing measurable.
|
||||||
|
2. G1 being allowed to grow toward `MaxHeapSize`. Capping the heap at `-Xmx256m` cut peak RSS from ~492 MB to ~294 MB with about the same wall time.
|
||||||
|
|
||||||
|
## Advice
|
||||||
|
|
||||||
|
* Nothing to do if you already set a collector explicitly.
|
||||||
|
* If you were relying on the implicit Serial in small containers and you are throughput- or footprint-bound, pin it: `-XX:+UseSerialGC`.
|
||||||
|
* If you are happy with G1, **set `-Xmx`** (or `-XX:MaxRAMPercentage`) so the heap does not sprawl to the default maximum.
|
||||||
|
* Note `-XX:InitiatingHeapOccupancyPercent` is deprecated in 27 in favour of `-XX:G1IHOP` ([60](output/60-removed-options.txt)).
|
||||||
|
|
||||||
|
Prev: [1. What shipped](01-what-shipped.md) · Next: [3. Compact object headers](03-compact-headers.md)
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# 3. Compact object headers are the default (JEP 534)
|
||||||
|
|
||||||
|
Prev: [2. G1](02-g1-default.md) · Next: [4. Post-quantum TLS](04-post-quantum-tls.md)
|
||||||
|
|
||||||
|
Every Java object starts with a header. On JDK 26 with default settings it is **12 bytes** (8 bytes mark word plus a 4-byte compressed class pointer).
|
||||||
|
With compact object headers the two are squeezed into **8 bytes**. JEP 534 makes that the default in 27; `-XX:-UseCompactObjectHeaders` turns it off.
|
||||||
|
|
||||||
|
## Measured with JOL
|
||||||
|
|
||||||
|
[`scripts/object-headers.sh`](../scripts/object-headers.sh) downloads JOL 0.17 (sha1-checked) and runs [`HeaderDemo`](../object-headers/src/HeaderDemo.java) three ways.
|
||||||
|
The summary is [15](output/15-headers-summary.txt); raw runs are [10](output/10-headers-jdk26.txt) (26), [11](output/11-headers-jdk27.txt) (27),
|
||||||
|
[12](output/12-headers-jdk27-opt-out.txt) (27 with the opt-out, which reproduces 26 exactly).
|
||||||
|
|
||||||
|
| Object | JDK 26 | JDK 27 |
|
||||||
|
|---|---|---|
|
||||||
|
| `Object` | 16 B | 8 B |
|
||||||
|
| `Point { int x; int y; }` | 24 B | 16 B |
|
||||||
|
| `Order { long id; int quantity; boolean paid; String customer; }` | 32 B | 32 B |
|
||||||
|
| boxed `Long` | 24 B | 16 B |
|
||||||
|
| boxed `Integer` | 16 B | 16 B |
|
||||||
|
| `int[3]` | 32 B | 24 B |
|
||||||
|
| 1,000,000 boxed `Long` in an `ArrayList` | 28.9 MB | 20.9 MB |
|
||||||
|
| 1,000,000 `Point` in an `ArrayList` | 28.9 MB | 20.9 MB |
|
||||||
|
| 1,000,000 boxed `Integer` in an `ArrayList` | 20.9 MB | 20.9 MB |
|
||||||
|
|
||||||
|
The pattern to notice: saving 4 bytes only helps when it crosses an 8-byte alignment boundary. `Order` and boxed `Integer` do not move, because padding
|
||||||
|
was already absorbing the difference. Your heap will not shrink by "4 bytes times object count"; it shrinks by the number of objects that were sitting just past a boundary.
|
||||||
|
|
||||||
|
## Two things that bite
|
||||||
|
|
||||||
|
* **JOL 0.17 cannot inspect a `record`.** `Unsafe.objectFieldOffset` refuses records ([13](output/13-jol-record-failure.txt)). The workaround
|
||||||
|
`-Djol.magicFieldOffset=true` prints a layout but shows the header mark as `N/A` ([14](output/14-jol-record-workaround.txt)).
|
||||||
|
* **Anything that hard-codes 12 bytes** (off-heap size estimators, "object overhead" constants in capacity plans, tests that assert `Instance size`) is now wrong by default.
|
||||||
|
`-XX:+UseCompressedClassPointers` is also no longer an option in 27: the JVM prints "Ignoring option UseCompressedClassPointers; support was removed in 27.0" ([60](output/60-removed-options.txt)).
|
||||||
|
|
||||||
|
Prev: [2. G1](02-g1-default.md) · Next: [4. Post-quantum TLS](04-post-quantum-tls.md)
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# 4. Post-quantum hybrid key exchange in TLS 1.3 (JEP 527)
|
||||||
|
|
||||||
|
Prev: [3. Compact headers](03-compact-headers.md) · Next: [5. Primitive patterns](05-primitive-patterns.md)
|
||||||
|
|
||||||
|
JDK 27's default TLS 1.3 client offers the hybrid group **X25519MLKEM768** first: an ordinary X25519 exchange and an ML-KEM-768 key encapsulation combined, so a
|
||||||
|
recorded session stays safe even if a large quantum computer later breaks X25519.
|
||||||
|
|
||||||
|
## Who negotiates what
|
||||||
|
|
||||||
|
[`scripts/tls-pq.sh`](../scripts/tls-pq.sh) starts a loopback TLS server with [`TlsPeer`](../jep-tour/src/TlsPeer.java) on one JDK and connects with a client on
|
||||||
|
the other, for all four pairings, with `-Djavax.net.debug=ssl:handshake` ([30](output/30-tls-groups-matrix.txt)).
|
||||||
|
|
||||||
|
| Client | Server | Group selected |
|
||||||
|
|---|---|---|
|
||||||
|
| 26 | 26 | x25519 |
|
||||||
|
| 27 | 27 | **X25519MLKEM768** |
|
||||||
|
| 27 | 26 | x25519 (the 26 server does not know the hybrid group) |
|
||||||
|
| 26 | 27 | x25519 (the 26 client never offered it) |
|
||||||
|
|
||||||
|
Nothing fails when the peers disagree: the hybrid only happens when both ends are on 27 (or another stack that supports it).
|
||||||
|
|
||||||
|
## The catch: a bigger first packet
|
||||||
|
|
||||||
|
[`ClientHelloSize`](../jep-tour/src/ClientHelloSize.java) captures the first TLS record ([31](output/31-tls-clienthello-size.txt)):
|
||||||
|
|
||||||
|
| Client | ClientHello record | Fits one TCP segment (1460-byte MSS)? |
|
||||||
|
|---|---|---|
|
||||||
|
| JDK 26 | 417 bytes | yes |
|
||||||
|
| JDK 27 | 1573 bytes | **no** |
|
||||||
|
| JDK 27, `-Djdk.tls.namedGroups=x25519,secp256r1` | 408 bytes | yes |
|
||||||
|
|
||||||
|
The ML-KEM public key alone is 1184 bytes, so the ClientHello no longer fits in one segment. Middleboxes that mishandle a ClientHello spanning segments are the
|
||||||
|
known failure mode of large post-quantum ClientHellos; **that failure was not observed here** (loopback has no middleboxes). If you meet it, the opt-out is the system
|
||||||
|
property above, and the client then behaves like 26 ([32](output/32-tls-opt-out.txt)).
|
||||||
|
|
||||||
|
Prev: [3. Compact headers](03-compact-headers.md) · Next: [5. Primitive patterns](05-primitive-patterns.md)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# 5. Primitive types in patterns (JEP 532, fifth preview)
|
||||||
|
|
||||||
|
Prev: [4. Post-quantum TLS](04-post-quantum-tls.md) · Next: [6. Lazy constants](06-lazy-constants.md)
|
||||||
|
|
||||||
|
Compile and run with `--enable-preview --release 27`. Source: [`PrimitivePatterns`](../jep-tour/src/PrimitivePatterns.java), transcript [23](output/23-primitive-patterns.txt).
|
||||||
|
|
||||||
|
## The idea in one sentence
|
||||||
|
|
||||||
|
A pattern may now name a primitive type, and the test is **"can this value be converted to that type without losing information?"**. It is an exactness test, not a cast.
|
||||||
|
|
||||||
|
```java
|
||||||
|
int small = 100, big = 300;
|
||||||
|
small instanceof byte // true : 100 fits
|
||||||
|
big instanceof byte // false : a byte holds -128..127
|
||||||
|
16_777_216 instanceof float // true
|
||||||
|
16_777_217 instanceof float // false : a float has a 24-bit significand
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also `switch` on a `long`, `float`, `double` or `boolean`, use `case int` against an `Integer`, and put a narrowing primitive in a record pattern:
|
||||||
|
`Reading(byte c)` matches `Reading[celsius=36]` but not `Reading[celsius=4000]` ([23](output/23-primitive-patterns.txt)).
|
||||||
|
|
||||||
|
## The compile error you will meet
|
||||||
|
|
||||||
|
Dominance rules apply. Put `case Integer i` before `case int primitive` and the second label can never match:
|
||||||
|
|
||||||
|
```
|
||||||
|
error: this case label is dominated by a preceding case label
|
||||||
|
```
|
||||||
|
|
||||||
|
The real text is in [20-broken-dominated.txt](output/20-broken-dominated.txt); source in [`Dominated.java`](../jep-tour/src/broken/Dominated.java).
|
||||||
|
|
||||||
|
Prev: [4. Post-quantum TLS](04-post-quantum-tls.md) · Next: [6. Lazy constants](06-lazy-constants.md)
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# 6. Lazy constants (JEP 531, third preview)
|
||||||
|
|
||||||
|
Prev: [5. Primitive patterns](05-primitive-patterns.md) · Next: [7. Structured concurrency](07-structured-concurrency.md)
|
||||||
|
|
||||||
|
Compile and run with `--enable-preview --release 27`. Source: [`LazyDemo`](../jep-tour/src/LazyDemo.java), transcript [22](output/22-lazy.txt).
|
||||||
|
|
||||||
|
A **lazy constant** holds a value that is computed the first time somebody asks for it, exactly once, even when many threads ask at the same instant,
|
||||||
|
after which the JVM may treat it like a `final` field. It replaces the hand-written holder-class idiom and the double-checked-locking field.
|
||||||
|
|
||||||
|
```java
|
||||||
|
static final LazyConstant<Settings> SETTINGS = LazyConstant.of(LazyDemo::loadSettings);
|
||||||
|
Settings s = SETTINGS.get(); // computes on first call, returns the same instance afterwards
|
||||||
|
```
|
||||||
|
|
||||||
|
What the transcript shows, each backed by an assertion: the supplier ran once; 64 virtual threads racing on `get()` caused one initialisation; `List.ofLazy(5, i -> ...)`
|
||||||
|
computed only element 3 after `get(3)`; `Map.ofLazy(keys, f)` computed only the requested key; `Set.ofLazy(Set.of(2..7), isPrime)` answers `contains` on demand.
|
||||||
|
|
||||||
|
## What changed since 26 (the compile errors)
|
||||||
|
|
||||||
|
* `LazyConstant.orElse(...)` and `isInitialized()` are **gone** in 27 ([20-broken-lazy26.txt](output/20-broken-lazy26.txt); the class is `LazyConstant` in both, the old `StableValue` was already removed in 26 - [52](output/52-api-diff-25-to-26.txt)).
|
||||||
|
* `Set.ofLazy(Set, Predicate)` is new in 27 ([62](output/62-new-api.txt), [51](output/51-api-diff-26-to-27.txt)).
|
||||||
|
|
||||||
|
Prev: [5. Primitive patterns](05-primitive-patterns.md) · Next: [7. Structured concurrency](07-structured-concurrency.md)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# 7. Structured concurrency (JEP 533, seventh preview)
|
||||||
|
|
||||||
|
Prev: [6. Lazy constants](06-lazy-constants.md) · Next: [8. PEM](08-pem-api.md)
|
||||||
|
|
||||||
|
Compile and run with `--enable-preview --release 27`. Source: [`StructuredDemo`](../jep-tour/src/StructuredDemo.java), transcript [21](output/21-structured.txt).
|
||||||
|
|
||||||
|
The 27 API, as exercised:
|
||||||
|
|
||||||
|
```java
|
||||||
|
try (var scope = StructuredTaskScope.open(Joiner.<String>allSuccessfulOrThrow())) {
|
||||||
|
scope.fork(StructuredDemo::fetchPrice);
|
||||||
|
scope.fork(StructuredDemo::fetchStock);
|
||||||
|
List<String> results = scope.join(); // [price=42, stock=7], in fork order
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* A failing subtask makes `join()` throw `ExecutionException` with the original as its cause.
|
||||||
|
* **New in 27:** `StructuredTaskScope` and `Joiner` gained a third type parameter, the exception `join()` throws. `Joiner.<String, OrderFailed>allSuccessfulOrThrow(OrderFailed::new)` makes `join()` throw *your* type.
|
||||||
|
* A timeout is a scope option (`cfg -> cfg.withTimeout(Duration.ofMillis(100))`) and surfaces as `CancelledByTimeoutException`.
|
||||||
|
|
||||||
|
## What broke from 26
|
||||||
|
|
||||||
|
[`StructuredScope26.java`](../jep-tour/src/broken/StructuredScope26.java) compiles on 26 and fails on 27 with three errors ([20-broken-structuredscope26.txt](output/20-broken-structuredscope26.txt)):
|
||||||
|
`wrong number of type arguments; required 3`, and `FailedException` / `TimeoutException` no longer exist as nested classes
|
||||||
|
([51](output/51-api-diff-26-to-27.txt): removed `StructuredTaskScope$FailedException`, `$TimeoutException`; added `$CancelledByTimeoutException`).
|
||||||
|
|
||||||
|
Prev: [6. Lazy constants](06-lazy-constants.md) · Next: [8. PEM](08-pem-api.md)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# 8. PEM encodings (JEP 538, third preview)
|
||||||
|
|
||||||
|
Prev: [7. Structured concurrency](07-structured-concurrency.md) · Next: [9. JFR redaction and the Vector API](09-jfr-redaction-and-vector.md)
|
||||||
|
|
||||||
|
Compile and run with `--enable-preview --release 27`. Source: [`PemDemo`](../jep-tour/src/PemDemo.java), transcript [24](output/24-pem.txt).
|
||||||
|
|
||||||
|
PEM is the `-----BEGIN ...-----` text format that every key and certificate file on a Linux box uses. The preview API replaces hand-rolled Base64 and header strings:
|
||||||
|
|
||||||
|
```java
|
||||||
|
String pem = PEMEncoder.of().encodeToString(keyPair.getPublic()); // -----BEGIN PUBLIC KEY-----
|
||||||
|
PublicKey back = PEMDecoder.of().decode(pem, PublicKey.class);
|
||||||
|
String enc = PEMEncoder.of().withEncryption(password).encodeToString(privateKey); // ENCRYPTED PRIVATE KEY
|
||||||
|
```
|
||||||
|
|
||||||
|
Each round trip is asserted in the demo, including: without the password `decode` returns a still-encrypted `EncryptedPrivateKeyInfo`; with `withDecryption(password)` you get the original key back.
|
||||||
|
|
||||||
|
## The trap: `new PEM(type, byte[])` does not Base64-encode
|
||||||
|
|
||||||
|
The bytes you pass are treated as the **already Base64-encoded** content and written between the header and footer verbatim
|
||||||
|
([24](output/24-pem.txt): the demo prints `hello, pem` in clear between `BEGIN ANKURM DEMO` and `END ANKURM DEMO`). To get a valid PEM, Base64-encode first and pass that; then `content()` is the Base64 text and `decode()` gives your payload back. This cost a failed assertion while writing this repository.
|
||||||
|
|
||||||
|
## What moved between 26 and 27
|
||||||
|
|
||||||
|
[`Pem26Style.java`](../jep-tour/src/broken/Pem26Style.java) compiles on 26 and produces three errors on 27 ([20-broken-pem26style.txt](output/20-broken-pem26style.txt)): `DEREncodable` became `BinaryEncodable`;
|
||||||
|
`PEMDecoder.withFactory(Provider)` is gone; `PEM.content()` now returns `byte[]` rather than `String`.
|
||||||
|
|
||||||
|
Prev: [7. Structured concurrency](07-structured-concurrency.md) · Next: [9. JFR redaction and the Vector API](09-jfr-redaction-and-vector.md)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# 9. JFR redaction (JEP 536) and the Vector API (JEP 537)
|
||||||
|
|
||||||
|
Prev: [8. PEM](08-pem-api.md) · Next: [10. Other 27 changes](10-other-27-changes.md)
|
||||||
|
|
||||||
|
## JFR redacts secrets in the recording by default
|
||||||
|
|
||||||
|
A JFR file records the JVM's command-line arguments, system properties and environment variables. Those routinely hold passwords and tokens, and recordings get attached to tickets.
|
||||||
|
[`scripts/jfr-redaction.sh`](../scripts/jfr-redaction.sh) starts an [`Idle`](../jep-tour/src/Idle.java) process with `-Dapi.token=abc123 -Dregion=ap-south-1 ... --password=hunter2 --user=ankur`
|
||||||
|
and `DB_PASSWORD=hunter2` in the environment, then prints what reached the file ([40](output/40-jfr-redaction.txt)):
|
||||||
|
|
||||||
|
| Configuration | `-Dapi.token` | `--password` | `DB_PASSWORD` | `region` |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| JDK 26 | abc123 | hunter2 | hunter2 | ap-south-1 |
|
||||||
|
| JDK 27 default | `[REDACTED]` | `[REDACTED]` | `[REDACTED]` | ap-south-1 |
|
||||||
|
| 27, `redact-argument=--user*,redact-key=region` | abc123 (exposed) | hunter2 (exposed) | hunter2 (exposed) | `[REDACTED]` |
|
||||||
|
| 27, `redact-argument=+--user*,redact-key=+region` | `[REDACTED]` | `[REDACTED]` | `[REDACTED]` | `[REDACTED]` |
|
||||||
|
| 27, `redact-argument=none,redact-key=none` | abc123 | hunter2 | hunter2 | ap-south-1 |
|
||||||
|
|
||||||
|
**The trap in row 3:** a list you supply *replaces* the built-in patterns. Without the `+` prefix you have silently turned the defaults off for everything you did not list.
|
||||||
|
|
||||||
|
## The Vector API is still incubating
|
||||||
|
|
||||||
|
[`VectorDemo`](../jep-tour/src/VectorDemo.java) ([25](output/25-vector.txt)) computes a dot product with and without `jdk.incubator.vector` and asserts they agree within 0.1%.
|
||||||
|
The same class file runs on 26 (eleventh incubator, JEP 529) and 27 (twelfth, JEP 537): the only public API change between them is that `VectorOperators` changed from `abstract` to `final`
|
||||||
|
([50](output/50-vector-api-surface.txt)). The preferred species is machine dependent (`S_512_BIT`, 16 float lanes, on the machine that wrote the transcript).
|
||||||
|
For the API itself see the ankurm.com guide: <https://ankurm.com/java-vector-api-jep-537-simd-guide/>.
|
||||||
|
|
||||||
|
Prev: [8. PEM](08-pem-api.md) · Next: [10. Other 27 changes](10-other-27-changes.md)
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# 10. The changes that are not JEPs but will still break a Monday
|
||||||
|
|
||||||
|
Prev: [9. JFR and Vector](09-jfr-redaction-and-vector.md) · Next: [11. Recap of Java 26](11-recap-26.md)
|
||||||
|
|
||||||
|
Script: [`scripts/other-changes.sh`](../scripts/other-changes.sh). All from real launches; each option runs `java <option> -version` on 26 and on 27 ([60](output/60-removed-options.txt)).
|
||||||
|
|
||||||
|
| Option / property | JDK 26 | JDK 27 |
|
||||||
|
|---|---|---|
|
||||||
|
| `-noverify`, `-Xverify:none` | deprecation warning | **`Unrecognized option`, the JVM does not start** |
|
||||||
|
| `-noclassgc` | deprecation warning | **`Unrecognized option`, the JVM does not start** |
|
||||||
|
| `-XX:+UseCompressedClassPointers` | deprecated in 25 | ignored: "support was removed in 27.0" |
|
||||||
|
| `-XX:InitiatingHeapOccupancyPercent=40` | accepted | deprecated; use `-XX:G1IHOP` |
|
||||||
|
| `-XX:+UseGraalJIT` (experimental) | JVMCI error | `Unrecognized VM option` (JVMCI hook removed) |
|
||||||
|
| `-Djdk.lang.Process.launchMechanism=VFORK` | deprecation banner | "has been removed. Switching to FORK instead" ([61](output/61-behaviour-changes.txt)) |
|
||||||
|
| `-Djava.locale.useOldISOCodes=true` | works, deprecated | ignored: `Locale("iw").language` is `he` ([61](output/61-behaviour-changes.txt)) |
|
||||||
|
|
||||||
|
**The point of the table:** rows 1-2 are the ones that stop a service from starting. They are old flags that live in Dockerfiles and `JAVA_TOOL_OPTIONS` long after anyone remembers why.
|
||||||
|
Grep your images before you upgrade.
|
||||||
|
|
||||||
|
## New public API worth knowing
|
||||||
|
|
||||||
|
Found by diffing `javap` dumps ([51](output/51-api-diff-26-to-27.txt)) and exercised once each in [`Api27`](../other-changes/src/Api27.java) ([62](output/62-new-api.txt)):
|
||||||
|
`Math.acosh/asinh/atanh`, `String.encodedLength(Charset)` (length in bytes without allocating the array), `BigDecimal.rootn`, `KeyStore.getCreationInstant`.
|
||||||
|
|
||||||
|
Prev: [9. JFR and Vector](09-jfr-redaction-and-vector.md) · Next: [11. Recap of Java 26](11-recap-26.md)
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# 11. Recap: the Java 26 changes you skipped
|
||||||
|
|
||||||
|
Prev: [10. Other 27 changes](10-other-27-changes.md) · Next: [12. Version lanes 25 - 29](12-lanes-25-to-29.md)
|
||||||
|
|
||||||
|
Java 26 (GA 2026-03-17) is also a non-LTS release. If you are on 25 and going to 27 you meet all of it at once. Script: [`scripts/recap26.sh`](../scripts/recap26.sh).
|
||||||
|
|
||||||
|
## JEP 500: final means final
|
||||||
|
|
||||||
|
Reflection has always let `Field.setAccessible(true)` followed by `Field.setInt(...)` overwrite a `final` field. Source: [`FinalFieldMutation`](../recap26/src/FinalFieldMutation.java), transcript [70](output/70-final-field-mutation.txt).
|
||||||
|
|
||||||
|
| Run | Result |
|
||||||
|
|---|---|
|
||||||
|
| JDK 25 | silent |
|
||||||
|
| JDK 26 default | three `WARNING:` lines, then it proceeds |
|
||||||
|
| JDK 26 `--enable-final-field-mutation=ALL-UNNAMED` | silent (the opt-in the warning names) |
|
||||||
|
| JDK 26 `--illegal-final-field-mutation=deny` | `IllegalAccessException`: "... is not allowed to mutate final fields" |
|
||||||
|
| JDK 26 `--illegal-final-field-mutation=debug` | the warning plus the stack trace of the offending call |
|
||||||
|
|
||||||
|
`--illegal-final-field-mutation` takes `allow`, `warn`, `debug` or `deny`. A JFR event, `jdk.FinalFieldMutation`, is recorded with default settings whenever a mutation is *permitted*
|
||||||
|
(one event, with the declaring class and field name); under `deny` there is nothing to record.
|
||||||
|
|
||||||
|
Even when it works it misleads: the field reads 9090 but `port()` still returns 8080, because `final int port = 8080` is a constant variable and javac copied the literal into the method.
|
||||||
|
|
||||||
|
Who this hits: serialisation libraries, mocking frameworks, dependency-injection containers and test code that set `final` fields. Run your test suite once with `deny` on 26 or later.
|
||||||
|
|
||||||
|
## JEP 504: the Applet API is gone
|
||||||
|
|
||||||
|
`java.applet.*`, `javax.swing.JApplet` and the `AppletInitializer` overload of `java.beans.Beans.instantiate` were removed ([52](output/52-api-diff-25-to-26.txt)).
|
||||||
|
[`AppletGone.java`](../recap26/src/broken/AppletGone.java) compiles on 25 with a `[removal]` warning and fails on 26 with `package java.applet does not exist` ([73](output/73-removed-in-26.txt)).
|
||||||
|
In the same transcript, [`ThreadStopGone.java`](../recap26/src/broken/ThreadStopGone.java): `Thread.stop()` (which has thrown `UnsupportedOperationException` since 20) no longer exists at all: `cannot find symbol`.
|
||||||
|
|
||||||
|
## JEP 516: AOT object caching with any GC
|
||||||
|
|
||||||
|
The AOT cache (JEP 483 / 514 / 515 in 24 and 25) stores pre-parsed and pre-linked classes and, in some configurations, pre-built heap objects such as the module graph.
|
||||||
|
Until 26 the heap-object part needed G1. Workflow used here: `-XX:AOTMode=record -XX:AOTConfiguration=...`, then `-XX:AOTMode=create -XX:AOTCache=...`, then run with `-XX:AOTCache=...`.
|
||||||
|
|
||||||
|
What the JVM's own log says ([71](output/71-aot-cache-matrix.txt)):
|
||||||
|
|
||||||
|
| Cache built under | Run under | JDK 25 | JDK 26 / 27 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| G1 | G1 or Serial | heap objects used | heap objects used |
|
||||||
|
| G1 | ZGC | cache refused | cache refused |
|
||||||
|
| ZGC | ZGC | cache used, **heap objects not used** | **heap objects used** |
|
||||||
|
| ZGC | G1 or Serial | cache refused | cache refused |
|
||||||
|
|
||||||
|
So "any GC" means *each* GC can now benefit, not that one cache serves every GC: the cache records whether compressed oops were on
|
||||||
|
(`The saved state of UseCompressedOops (1) is different from runtime (0), CDS will be disabled`), and ZGC does not use them. Build the cache under the collector you deploy with.
|
||||||
|
|
||||||
|
Startup, median of 9 runs, indicative only ([72](output/72-aot-startup.txt)): with ZGC, JDK 25 barely moves (about 83 -> 81 ms, because no heap objects are used), while JDK 26 and 27 go from about 62 to about 40 ms; with G1 all three go from about 60 to about 35-37 ms.
|
||||||
|
A 27 JVM has a new diagnostic flag `AOTCompatibleOopCompression`; setting it at cache creation did not make a G1-built cache load under ZGC in one attempt, and nothing more was investigated.
|
||||||
|
|
||||||
|
## JEP 517: HTTP/3 in the HTTP client
|
||||||
|
|
||||||
|
`HttpClient.Version.HTTP_3`, `HttpOption.H3_DISCOVERY` and `HttpOption.Http3DiscoveryMode` (`ANY`, `ALT_SVC`, `HTTP_3_URI_ONLY`) are the API surface ([75](output/75-http3-fallback.txt), from `javap`).
|
||||||
|
The sandbox has no UDP egress, so no real HTTP/3 exchange was run. What [`Http3Fallback`](../recap26/src/Http3Fallback.java) shows instead: a client that *prefers* HTTP/3 against a TLS server that only speaks HTTP/1.1
|
||||||
|
falls back and `response.version()` says `HTTP_1_1`; a request pinned to `HTTP_3_URI_ONLY` fails with `HttpConnectTimeoutException: quic handshake timeout`.
|
||||||
|
|
||||||
|
## JEP 522: G1 throughput
|
||||||
|
|
||||||
|
"Improve throughput by reducing synchronization between application and GC threads." Not benchmarked here; it is a claim from the JEP, and it is the reason 26 is a fairer G1 baseline than 25 in [chapter 2](02-g1-default.md).
|
||||||
|
|
||||||
|
## New public API without a JEP
|
||||||
|
|
||||||
|
Verified by javap diff ([52](output/52-api-diff-25-to-26.txt)) and asserted in [`Api26`](../recap26/src/Api26.java) ([74](output/74-api26.txt)): `String.equalsFoldCase`, `compareToFoldCase` and `UNICODE_CASEFOLD_ORDER`
|
||||||
|
(`"STRASSE".equalsFoldCase("straße")` is true; `equalsIgnoreCase` says false), `UUID.ofEpochMillis` (a version-7 UUID for a given instant), `Process` implementing `Closeable`,
|
||||||
|
`MemoryMXBean.getTotalGcCpuTime()`, `ByteOrder` becoming an enum, `HPKEParameterSpec` (Hybrid Public Key Encryption), and the Unicode 17 blocks in `Character.UnicodeBlock`.
|
||||||
|
|
||||||
|
Prev: [10. Other 27 changes](10-other-27-changes.md) · Next: [12. Version lanes 25 - 29](12-lanes-25-to-29.md)
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# 12. Version lanes: 25 (LTS) to 29 (next LTS)
|
||||||
|
|
||||||
|
Ordinals in the 25 column come from Java 25's published JEP list (470, 502, 505, 507, 508), not from a run here; the existence of `StableValue` and `PEMRecord` in 25 and their absence in 26 *was* observed ([52](output/52-api-diff-25-to-26.txt)).
|
||||||
|
|
||||||
|
Prev: [11. Recap of Java 26](11-recap-26.md) · Next: [13. Upgrade checklist](13-upgrade-checklist.md)
|
||||||
|
|
||||||
|
Dates and support windows are from Oracle's Java SE Support Roadmap, read on 2026-09-21: <https://www.oracle.com/java/technologies/java-se-support-roadmap.html>.
|
||||||
|
Feature notes link to the chapter that has the run.
|
||||||
|
|
||||||
|
| Release | GA | Kind | Premier support until | What to read here |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 25 | Sept 2025 | **LTS** | Sept 2030 | the baseline for every "broke on 26/27" demo in this repository |
|
||||||
|
| 26 | March 2026 | non-LTS | Sept 2026 | [chapter 11](11-recap-26.md): final-field warnings, Applet removal, HTTP/3, AOT with any GC |
|
||||||
|
| 27 | Sept 2026 | non-LTS | March 2027 | chapters [2](02-g1-default.md) - [10](10-other-27-changes.md): G1 default, compact headers, PQ TLS, JFR redaction |
|
||||||
|
| 28 | March 2027 | non-LTS (planned) | Sept 2027 | not covered: not released |
|
||||||
|
| 29 | Sept 2027 | **LTS (planned)** | Sept 2032 | not covered: not released |
|
||||||
|
|
||||||
|
Oracle's stated cadence: "Oracle intends to make future LTS releases every two years meaning the next planned LTS release is Java 29 in September 2027."
|
||||||
|
Other vendors set their own support lengths for non-LTS builds; check the vendor you deploy.
|
||||||
|
|
||||||
|
The preview-feature ladder across the lane (each preview must be recompiled and can break the next release, see [20-broken-*](output/)):
|
||||||
|
|
||||||
|
| Feature | 25 | 26 | 27 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Structured Concurrency | 5th preview | 6th preview (JEP 525) | 7th preview (JEP 533), API reshaped |
|
||||||
|
| Lazy Constants (was Stable Values in 25) | Stable Values, preview | 2nd preview (JEP 526) | 3rd preview (JEP 531), `orElse`/`isInitialized` removed |
|
||||||
|
| PEM encodings | 1st preview (`PEMRecord`) | 2nd preview (JEP 524) | 3rd preview (JEP 538), `DEREncodable` renamed |
|
||||||
|
| Primitive patterns | 3rd preview | 4th preview (JEP 530) | 5th preview (JEP 532) |
|
||||||
|
| Vector API | 10th incubator | 11th (JEP 529) | 12th (JEP 537) |
|
||||||
|
|
||||||
|
Prev: [11. Recap of Java 26](11-recap-26.md) · Next: [13. Upgrade checklist](13-upgrade-checklist.md)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# 13. Upgrade checklist: 25 to 27 (or to 26)
|
||||||
|
|
||||||
|
Prev: [12. Version lanes](12-lanes-25-to-29.md) · [Back to the README](../README.md)
|
||||||
|
|
||||||
|
Ordered by "will stop a service from starting" first.
|
||||||
|
|
||||||
|
1. **Grep for flags that no longer exist.** `-noverify`, `-Xverify:none` and `-noclassgc` make a 27 JVM refuse to start ([60](output/60-removed-options.txt)). Look in Dockerfiles, `JAVA_TOOL_OPTIONS`, systemd units and start scripts.
|
||||||
|
2. **Pin your collector in small containers.** If nobody sets one, 27 gives you G1 on one CPU, where 26 gave you Serial ([02](02-g1-default.md)). Decide on purpose, and set `-Xmx` or `-XX:MaxRAMPercentage`.
|
||||||
|
3. **Run your tests once with `--illegal-final-field-mutation=deny` on 26 or later.** Mocking, DI and serialisation libraries are the usual offenders ([11](11-recap-26.md)).
|
||||||
|
4. **Compile everything on 26 and 27.** `java.applet`, `JApplet` and `Thread.stop()` are gone ([73](output/73-removed-in-26.txt)).
|
||||||
|
5. **Test TLS through your real network path.** JDK 27 clients send a 1573-byte ClientHello ([04](04-post-quantum-tls.md)). If a middlebox objects, `-Djdk.tls.namedGroups=x25519,secp256r1` restores the old behaviour.
|
||||||
|
6. **Search for hard-coded object sizes.** Estimators, capacity plans and tests that assume a 12-byte header ([03](03-compact-headers.md)).
|
||||||
|
7. **Do not ship preview APIs from a library.** Structured concurrency, lazy constants and PEM changed API between 26 and 27, and primitive patterns is now on its fifth preview ([12](12-lanes-25-to-29.md)).
|
||||||
|
8. **Check custom JFR redaction settings.** A list without `+` replaces the defaults ([09](09-jfr-redaction-and-vector.md)).
|
||||||
|
9. **Rebuild AOT caches per JDK and per collector** ([11](11-recap-26.md)).
|
||||||
|
10. **Decide whether to move at all.** 26 and 27 are non-LTS: 26's premier support ends September 2026 and 27's in March 2027. Staying on 25 and planning for 29 is a legitimate answer ([12](12-lanes-25-to-29.md)).
|
||||||
|
|
||||||
|
Prev: [12. Version lanes](12-lanes-25-to-29.md) · [Back to the README](../README.md)
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# JEP 523: which collector does the JVM pick when you do not choose one?
|
||||||
|
# Same class files, same vendor (Amazon Corretto), same 2 GB memory limit. Only the JDK version and the CPU limit change.
|
||||||
|
|
||||||
|
=== amazoncorretto:26 --cpus=1 --memory=2g
|
||||||
|
$ docker run --rm --cpus=1 --memory=2g amazoncorretto:26 java -cp /app GcReport
|
||||||
|
java.version = 26.0.2.1
|
||||||
|
availableCpus = 1
|
||||||
|
maxHeapMB = 511
|
||||||
|
collector beans = Copy, MarkSweepCompact
|
||||||
|
UseG1GC = false (origin DEFAULT)
|
||||||
|
UseSerialGC = true (origin ERGONOMIC)
|
||||||
|
UseParallelGC = false (origin DEFAULT)
|
||||||
|
|
||||||
|
=== amazoncorretto:27 --cpus=1 --memory=2g
|
||||||
|
$ docker run --rm --cpus=1 --memory=2g amazoncorretto:27 java -cp /app GcReport
|
||||||
|
java.version = 27
|
||||||
|
availableCpus = 1
|
||||||
|
maxHeapMB = 512
|
||||||
|
collector beans = G1 Young Generation, G1 Concurrent GC, G1 Old Generation
|
||||||
|
UseG1GC = true (origin ERGONOMIC)
|
||||||
|
UseSerialGC = false (origin DEFAULT)
|
||||||
|
UseParallelGC = false (origin DEFAULT)
|
||||||
|
|
||||||
|
=== amazoncorretto:26 --cpus=2 --memory=2g
|
||||||
|
$ docker run --rm --cpus=2 --memory=2g amazoncorretto:26 java -cp /app GcReport
|
||||||
|
java.version = 26.0.2.1
|
||||||
|
availableCpus = 2
|
||||||
|
maxHeapMB = 512
|
||||||
|
collector beans = G1 Young Generation, G1 Concurrent GC, G1 Old Generation
|
||||||
|
UseG1GC = true (origin ERGONOMIC)
|
||||||
|
UseSerialGC = false (origin DEFAULT)
|
||||||
|
UseParallelGC = false (origin DEFAULT)
|
||||||
|
|
||||||
|
=== amazoncorretto:27 --cpus=2 --memory=2g
|
||||||
|
$ docker run --rm --cpus=2 --memory=2g amazoncorretto:27 java -cp /app GcReport
|
||||||
|
java.version = 27
|
||||||
|
availableCpus = 2
|
||||||
|
maxHeapMB = 512
|
||||||
|
collector beans = G1 Young Generation, G1 Concurrent GC, G1 Old Generation
|
||||||
|
UseG1GC = true (origin ERGONOMIC)
|
||||||
|
UseSerialGC = false (origin DEFAULT)
|
||||||
|
UseParallelGC = false (origin DEFAULT)
|
||||||
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# The other half of the old rule: memory. The old heuristic wanted at least 2 CPUs AND about 1792 MB.
|
||||||
|
# With 2 CPUs the CPU half of the rule is satisfied, so only the memory limit differs between these runs.
|
||||||
|
|
||||||
|
=== amazoncorretto:26 --cpus=2 --memory=1g
|
||||||
|
$ docker run --rm --cpus=2 --memory=1g amazoncorretto:26 java -cp /app GcReport
|
||||||
|
java.version = 26.0.2.1
|
||||||
|
availableCpus = 2
|
||||||
|
maxHeapMB = 255
|
||||||
|
collector beans = Copy, MarkSweepCompact
|
||||||
|
UseG1GC = false (origin DEFAULT)
|
||||||
|
UseSerialGC = true (origin ERGONOMIC)
|
||||||
|
UseParallelGC = false (origin DEFAULT)
|
||||||
|
|
||||||
|
=== amazoncorretto:27 --cpus=2 --memory=1g
|
||||||
|
$ docker run --rm --cpus=2 --memory=1g amazoncorretto:27 java -cp /app GcReport
|
||||||
|
java.version = 27
|
||||||
|
availableCpus = 2
|
||||||
|
maxHeapMB = 256
|
||||||
|
collector beans = G1 Young Generation, G1 Concurrent GC, G1 Old Generation
|
||||||
|
UseG1GC = true (origin ERGONOMIC)
|
||||||
|
UseSerialGC = false (origin DEFAULT)
|
||||||
|
UseParallelGC = false (origin DEFAULT)
|
||||||
|
|
||||||
|
=== amazoncorretto:26 --cpus=2 --memory=2g
|
||||||
|
$ docker run --rm --cpus=2 --memory=2g amazoncorretto:26 java -cp /app GcReport
|
||||||
|
java.version = 26.0.2.1
|
||||||
|
availableCpus = 2
|
||||||
|
maxHeapMB = 512
|
||||||
|
collector beans = G1 Young Generation, G1 Concurrent GC, G1 Old Generation
|
||||||
|
UseG1GC = true (origin ERGONOMIC)
|
||||||
|
UseSerialGC = false (origin DEFAULT)
|
||||||
|
UseParallelGC = false (origin DEFAULT)
|
||||||
|
|
||||||
|
=== amazoncorretto:27 --cpus=2 --memory=2g
|
||||||
|
$ docker run --rm --cpus=2 --memory=2g amazoncorretto:27 java -cp /app GcReport
|
||||||
|
java.version = 27
|
||||||
|
availableCpus = 2
|
||||||
|
maxHeapMB = 512
|
||||||
|
collector beans = G1 Young Generation, G1 Concurrent GC, G1 Old Generation
|
||||||
|
UseG1GC = true (origin ERGONOMIC)
|
||||||
|
UseSerialGC = false (origin DEFAULT)
|
||||||
|
UseParallelGC = false (origin DEFAULT)
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# The opt-out still works on 27: an explicit choice always wins over the JVM's.
|
||||||
|
|
||||||
|
=== amazoncorretto:27 --cpus=1 --memory=2g -XX:+UseSerialGC
|
||||||
|
$ docker run --rm --cpus=1 --memory=2g amazoncorretto:27 java -XX:+UseSerialGC -cp /app GcReport
|
||||||
|
java.version = 27
|
||||||
|
availableCpus = 1
|
||||||
|
maxHeapMB = 511
|
||||||
|
collector beans = Copy, MarkSweepCompact
|
||||||
|
UseG1GC = false (origin DEFAULT)
|
||||||
|
UseSerialGC = true (origin VM_CREATION)
|
||||||
|
UseParallelGC = false (origin DEFAULT)
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Allocation workload, 1 CPU / 2 GB container, 4000 batches x 50,000 objects, 3 runs per configuration (indicative, not a benchmark)
|
||||||
|
# 'default' means nobody passed a collector flag.
|
||||||
|
|
||||||
|
=== amazoncorretto:26 default run 1
|
||||||
|
wall time = 5980 ms
|
||||||
|
gc count / time = 1275 / 1831 ms
|
||||||
|
slowest batch = 115 ms
|
||||||
|
peak RSS = 557 MB
|
||||||
|
=== amazoncorretto:26 default run 2
|
||||||
|
wall time = 6145 ms
|
||||||
|
gc count / time = 1275 / 1872 ms
|
||||||
|
slowest batch = 142 ms
|
||||||
|
peak RSS = 557 MB
|
||||||
|
=== amazoncorretto:26 default run 3
|
||||||
|
wall time = 6402 ms
|
||||||
|
gc count / time = 1275 / 1861 ms
|
||||||
|
slowest batch = 129 ms
|
||||||
|
peak RSS = 557 MB
|
||||||
|
|
||||||
|
=== amazoncorretto:27 default run 1
|
||||||
|
wall time = 8196 ms
|
||||||
|
gc count / time = 140 / 1955 ms
|
||||||
|
slowest batch = 39 ms
|
||||||
|
peak RSS = 547 MB
|
||||||
|
=== amazoncorretto:27 default run 2
|
||||||
|
wall time = 8214 ms
|
||||||
|
gc count / time = 140 / 1970 ms
|
||||||
|
slowest batch = 41 ms
|
||||||
|
peak RSS = 547 MB
|
||||||
|
=== amazoncorretto:27 default run 3
|
||||||
|
wall time = 8432 ms
|
||||||
|
gc count / time = 140 / 2004 ms
|
||||||
|
slowest batch = 35 ms
|
||||||
|
peak RSS = 546 MB
|
||||||
|
|
||||||
|
=== amazoncorretto:27 -XX:+UseSerialGC run 1
|
||||||
|
wall time = 5306 ms
|
||||||
|
gc count / time = 1264 / 1669 ms
|
||||||
|
slowest batch = 125 ms
|
||||||
|
peak RSS = 557 MB
|
||||||
|
=== amazoncorretto:27 -XX:+UseSerialGC run 2
|
||||||
|
wall time = 4972 ms
|
||||||
|
gc count / time = 1264 / 1599 ms
|
||||||
|
slowest batch = 119 ms
|
||||||
|
peak RSS = 557 MB
|
||||||
|
=== amazoncorretto:27 -XX:+UseSerialGC run 3
|
||||||
|
wall time = 5698 ms
|
||||||
|
gc count / time = 1264 / 1713 ms
|
||||||
|
slowest batch = 128 ms
|
||||||
|
peak RSS = 557 MB
|
||||||
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Allocation workload, 1 CPU / 2 GB container, 400 batches x 50,000 objects, 3 runs per configuration (indicative, not a benchmark)
|
||||||
|
# 'default' means nobody passed a collector flag.
|
||||||
|
|
||||||
|
=== amazoncorretto:26 default run 1
|
||||||
|
wall time = 531 ms
|
||||||
|
gc count / time = 683 / 160 ms
|
||||||
|
slowest batch = 25 ms
|
||||||
|
peak RSS = 102 MB
|
||||||
|
=== amazoncorretto:26 default run 2
|
||||||
|
wall time = 536 ms
|
||||||
|
gc count / time = 683 / 165 ms
|
||||||
|
slowest batch = 22 ms
|
||||||
|
peak RSS = 103 MB
|
||||||
|
=== amazoncorretto:26 default run 3
|
||||||
|
wall time = 564 ms
|
||||||
|
gc count / time = 683 / 161 ms
|
||||||
|
slowest batch = 36 ms
|
||||||
|
peak RSS = 102 MB
|
||||||
|
|
||||||
|
=== amazoncorretto:27 default run 1
|
||||||
|
wall time = 1052 ms
|
||||||
|
gc count / time = 26 / 204 ms
|
||||||
|
slowest batch = 26 ms
|
||||||
|
peak RSS = 492 MB
|
||||||
|
=== amazoncorretto:27 default run 2
|
||||||
|
wall time = 974 ms
|
||||||
|
gc count / time = 26 / 192 ms
|
||||||
|
slowest batch = 25 ms
|
||||||
|
peak RSS = 492 MB
|
||||||
|
=== amazoncorretto:27 default run 3
|
||||||
|
wall time = 1041 ms
|
||||||
|
gc count / time = 26 / 234 ms
|
||||||
|
slowest batch = 36 ms
|
||||||
|
peak RSS = 492 MB
|
||||||
|
|
||||||
|
=== amazoncorretto:27 -XX:+UseSerialGC run 1
|
||||||
|
wall time = 528 ms
|
||||||
|
gc count / time = 668 / 147 ms
|
||||||
|
slowest batch = 23 ms
|
||||||
|
peak RSS = 98 MB
|
||||||
|
=== amazoncorretto:27 -XX:+UseSerialGC run 2
|
||||||
|
wall time = 522 ms
|
||||||
|
gc count / time = 668 / 157 ms
|
||||||
|
slowest batch = 27 ms
|
||||||
|
peak RSS = 98 MB
|
||||||
|
=== amazoncorretto:27 -XX:+UseSerialGC run 3
|
||||||
|
wall time = 588 ms
|
||||||
|
gc count / time = 668 / 157 ms
|
||||||
|
slowest batch = 48 ms
|
||||||
|
peak RSS = 97 MB
|
||||||
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# JDK 27, G1 (the default), 1 CPU / 2 GB container, 400 batches, 2 runs per configuration
|
||||||
|
|
||||||
|
=== flags that differ between 26 and 27 (from -XX:+PrintFlagsFinal)
|
||||||
|
--- amazoncorretto:26
|
||||||
|
uint ConcGCThreads = 0 {product} {default}
|
||||||
|
uintx MaxHeapFreeRatio = 70 {manageable} {default}
|
||||||
|
size_t MaxHeapSize = 536870912 {product} {ergonomic}
|
||||||
|
uintx MinHeapFreeRatio = 40 {manageable} {default}
|
||||||
|
uint ParallelGCThreads = 0 {product} {default}
|
||||||
|
bool UseCompactObjectHeaders = false {product lp64_product} {default}
|
||||||
|
bool UseCompressedClassPointers = true {product} {default}
|
||||||
|
--- amazoncorretto:27
|
||||||
|
uint ConcGCThreads = 1 {product} {ergonomic}
|
||||||
|
uint G1IHOP = 45 {product} {default}
|
||||||
|
uintx MaxHeapFreeRatio = 100 {manageable} {default}
|
||||||
|
size_t MaxHeapSize = 536870912 {product} {ergonomic}
|
||||||
|
uintx MinHeapFreeRatio = 0 {manageable} {default}
|
||||||
|
uint ParallelGCThreads = 1 {product} {default}
|
||||||
|
bool UseCompactObjectHeaders = true {product lp64_product} {default}
|
||||||
|
|
||||||
|
=== amazoncorretto:27 flags: [none]
|
||||||
|
wall time = 1064 ms
|
||||||
|
gc count / time = 26 / 208 ms
|
||||||
|
peak RSS = 492 MB
|
||||||
|
wall time = 1078 ms
|
||||||
|
gc count / time = 26 / 202 ms
|
||||||
|
peak RSS = 493 MB
|
||||||
|
=== amazoncorretto:27 flags: [-XX:MinHeapFreeRatio=40 -XX:MaxHeapFreeRatio=70]
|
||||||
|
wall time = 1115 ms
|
||||||
|
gc count / time = 26 / 268 ms
|
||||||
|
peak RSS = 492 MB
|
||||||
|
wall time = 982 ms
|
||||||
|
gc count / time = 26 / 199 ms
|
||||||
|
peak RSS = 492 MB
|
||||||
|
=== amazoncorretto:27 flags: [-Xmx256m]
|
||||||
|
wall time = 976 ms
|
||||||
|
gc count / time = 37 / 240 ms
|
||||||
|
peak RSS = 294 MB
|
||||||
|
wall time = 957 ms
|
||||||
|
gc count / time = 37 / 261 ms
|
||||||
|
peak RSS = 294 MB
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
java.version = 26.0.2.1
|
||||||
|
# VM mode: 64 bits
|
||||||
|
# Compressed references (oops): 0-bit shift
|
||||||
|
# Compressed class pointers: 0-bit shift and 0x3E000000 base
|
||||||
|
# Object alignment: 8 bytes
|
||||||
|
# ref, bool, byte, char, shrt, int, flt, lng, dbl
|
||||||
|
# Field sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8
|
||||||
|
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8
|
||||||
|
# Array base offsets: 16, 16, 16, 16, 16, 16, 16, 16, 16
|
||||||
|
|
||||||
|
|
||||||
|
=== java.lang.Object
|
||||||
|
java.lang.Object object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) N/A
|
||||||
|
8 4 (object header: class) N/A
|
||||||
|
12 4 (object alignment gap)
|
||||||
|
Instance size: 16 bytes
|
||||||
|
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
|
||||||
|
|
||||||
|
|
||||||
|
=== Point { int x; int y; }
|
||||||
|
HeaderDemo$Point object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) N/A
|
||||||
|
8 4 (object header: class) N/A
|
||||||
|
12 4 int Point.x N/A
|
||||||
|
16 4 int Point.y N/A
|
||||||
|
20 4 (object alignment gap)
|
||||||
|
Instance size: 24 bytes
|
||||||
|
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
|
||||||
|
|
||||||
|
|
||||||
|
=== Order { long id; int quantity; boolean paid; String customer; }
|
||||||
|
HeaderDemo$Order object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) N/A
|
||||||
|
8 4 (object header: class) N/A
|
||||||
|
12 4 int Order.quantity N/A
|
||||||
|
16 8 long Order.id N/A
|
||||||
|
24 1 boolean Order.paid N/A
|
||||||
|
25 3 (alignment/padding gap)
|
||||||
|
28 4 java.lang.String Order.customer N/A
|
||||||
|
Instance size: 32 bytes
|
||||||
|
Space losses: 3 bytes internal + 0 bytes external = 3 bytes total
|
||||||
|
|
||||||
|
|
||||||
|
=== java.lang.Long and java.lang.Integer (boxed values)
|
||||||
|
java.lang.Long object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
|
||||||
|
8 4 (object header: class) 0x001a15d0
|
||||||
|
12 4 (alignment/padding gap)
|
||||||
|
16 8 long Long.value 123456789
|
||||||
|
Instance size: 24 bytes
|
||||||
|
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
|
||||||
|
|
||||||
|
java.lang.Integer object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
|
||||||
|
8 4 (object header: class) 0x0019f570
|
||||||
|
12 4 int Integer.value 123456
|
||||||
|
Instance size: 16 bytes
|
||||||
|
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
|
||||||
|
|
||||||
|
|
||||||
|
=== int[3]
|
||||||
|
[I object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
|
||||||
|
8 4 (object header: class) 0x00174360
|
||||||
|
12 4 (array length) 3
|
||||||
|
16 12 int [I.<elements> N/A
|
||||||
|
28 4 (object alignment gap)
|
||||||
|
Instance size: 32 bytes
|
||||||
|
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
|
||||||
|
|
||||||
|
|
||||||
|
=== What it adds up to: one million objects
|
||||||
|
1,000,000 boxed Integer values in an ArrayList : 20,861,992 bytes
|
||||||
|
1,000,000 boxed Long values in an ArrayList : 28,861,992 bytes
|
||||||
|
1,000,000 Point objects in an ArrayList : 28,861,992 bytes
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
java.version = 27
|
||||||
|
# WARNING: Unable to attach Serviceability Agent. sun.jvm.hotspot.runtime.VM.isCompressedKlassPointersEnabled()
|
||||||
|
# VM mode: 64 bits
|
||||||
|
# Lilliput VM detected (experimental)
|
||||||
|
# Compressed references (oops): 3-bit shift
|
||||||
|
# Compressed class pointers: 3-bit shift
|
||||||
|
# WARNING | Compressed references base/shifts are guessed by the experiment!
|
||||||
|
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
|
||||||
|
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
|
||||||
|
# Object alignment: 8 bytes
|
||||||
|
# ref, bool, byte, char, shrt, int, flt, lng, dbl
|
||||||
|
# Field sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8
|
||||||
|
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8
|
||||||
|
# Array base offsets: 12, 12, 12, 12, 12, 12, 12, 16, 16
|
||||||
|
|
||||||
|
|
||||||
|
=== java.lang.Object
|
||||||
|
java.lang.Object object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) N/A
|
||||||
|
Instance size: 8 bytes
|
||||||
|
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
|
||||||
|
|
||||||
|
|
||||||
|
=== Point { int x; int y; }
|
||||||
|
HeaderDemo$Point object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) N/A
|
||||||
|
8 4 int Point.x N/A
|
||||||
|
12 4 int Point.y N/A
|
||||||
|
Instance size: 16 bytes
|
||||||
|
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
|
||||||
|
|
||||||
|
|
||||||
|
=== Order { long id; int quantity; boolean paid; String customer; }
|
||||||
|
HeaderDemo$Order object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) N/A
|
||||||
|
8 8 long Order.id N/A
|
||||||
|
16 4 int Order.quantity N/A
|
||||||
|
20 1 boolean Order.paid N/A
|
||||||
|
21 3 (alignment/padding gap)
|
||||||
|
24 4 java.lang.String Order.customer N/A
|
||||||
|
28 4 (object alignment gap)
|
||||||
|
Instance size: 32 bytes
|
||||||
|
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
|
||||||
|
|
||||||
|
|
||||||
|
=== java.lang.Long and java.lang.Integer (boxed values)
|
||||||
|
java.lang.Long object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) 0x001aa80000000001 (Lilliput)
|
||||||
|
8 8 long Long.value 123456789
|
||||||
|
Instance size: 16 bytes
|
||||||
|
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
|
||||||
|
|
||||||
|
java.lang.Integer object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) 0x001a840000000001 (Lilliput)
|
||||||
|
8 4 int Integer.value 123456
|
||||||
|
12 4 (object alignment gap)
|
||||||
|
Instance size: 16 bytes
|
||||||
|
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
|
||||||
|
|
||||||
|
|
||||||
|
=== int[3]
|
||||||
|
[I object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) 0x0017840000000001 (Lilliput)
|
||||||
|
8 4 (array length) 3
|
||||||
|
12 12 int [I.<elements> N/A
|
||||||
|
Instance size: 24 bytes
|
||||||
|
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
|
||||||
|
|
||||||
|
|
||||||
|
=== What it adds up to: one million objects
|
||||||
|
1,000,000 boxed Integer values in an ArrayList : 20,861,984 bytes
|
||||||
|
1,000,000 boxed Long values in an ArrayList : 20,861,984 bytes
|
||||||
|
1,000,000 Point objects in an ArrayList : 20,861,984 bytes
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
java.version = 27
|
||||||
|
# WARNING: Unable to attach Serviceability Agent. sun.jvm.hotspot.runtime.VM.isCompressedKlassPointersEnabled()
|
||||||
|
# VM mode: 64 bits
|
||||||
|
# Compressed references (oops): 3-bit shift
|
||||||
|
# Compressed class pointers: 3-bit shift
|
||||||
|
# WARNING | Compressed references base/shifts are guessed by the experiment!
|
||||||
|
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
|
||||||
|
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
|
||||||
|
# Object alignment: 8 bytes
|
||||||
|
# ref, bool, byte, char, shrt, int, flt, lng, dbl
|
||||||
|
# Field sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8
|
||||||
|
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8
|
||||||
|
# Array base offsets: 16, 16, 16, 16, 16, 16, 16, 16, 16
|
||||||
|
|
||||||
|
|
||||||
|
=== java.lang.Object
|
||||||
|
java.lang.Object object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) N/A
|
||||||
|
8 4 (object header: class) N/A
|
||||||
|
12 4 (object alignment gap)
|
||||||
|
Instance size: 16 bytes
|
||||||
|
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
|
||||||
|
|
||||||
|
|
||||||
|
=== Point { int x; int y; }
|
||||||
|
HeaderDemo$Point object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) N/A
|
||||||
|
8 4 (object header: class) N/A
|
||||||
|
12 4 int Point.x N/A
|
||||||
|
16 4 int Point.y N/A
|
||||||
|
20 4 (object alignment gap)
|
||||||
|
Instance size: 24 bytes
|
||||||
|
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
|
||||||
|
|
||||||
|
|
||||||
|
=== Order { long id; int quantity; boolean paid; String customer; }
|
||||||
|
HeaderDemo$Order object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) N/A
|
||||||
|
8 4 (object header: class) N/A
|
||||||
|
12 4 int Order.quantity N/A
|
||||||
|
16 8 long Order.id N/A
|
||||||
|
24 1 boolean Order.paid N/A
|
||||||
|
25 3 (alignment/padding gap)
|
||||||
|
28 4 java.lang.String Order.customer N/A
|
||||||
|
Instance size: 32 bytes
|
||||||
|
Space losses: 3 bytes internal + 0 bytes external = 3 bytes total
|
||||||
|
|
||||||
|
|
||||||
|
=== java.lang.Long and java.lang.Integer (boxed values)
|
||||||
|
java.lang.Long object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) 0x0000000000000009 (non-biasable; age: 1)
|
||||||
|
8 4 (object header: class) 0x001aa208
|
||||||
|
12 4 (alignment/padding gap)
|
||||||
|
16 8 long Long.value 123456789
|
||||||
|
Instance size: 24 bytes
|
||||||
|
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
|
||||||
|
|
||||||
|
java.lang.Integer object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
|
||||||
|
8 4 (object header: class) 0x001a7db8
|
||||||
|
12 4 int Integer.value 123456
|
||||||
|
Instance size: 16 bytes
|
||||||
|
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
|
||||||
|
|
||||||
|
|
||||||
|
=== int[3]
|
||||||
|
[I object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
|
||||||
|
8 4 (object header: class) 0x00176c00
|
||||||
|
12 4 (array length) 3
|
||||||
|
16 12 int [I.<elements> N/A
|
||||||
|
28 4 (object alignment gap)
|
||||||
|
Instance size: 32 bytes
|
||||||
|
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
|
||||||
|
|
||||||
|
|
||||||
|
=== What it adds up to: one million objects
|
||||||
|
1,000,000 boxed Integer values in an ArrayList : 20,861,992 bytes
|
||||||
|
1,000,000 boxed Long values in an ArrayList : 28,861,992 bytes
|
||||||
|
1,000,000 Point objects in an ArrayList : 28,861,992 bytes
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# WARNING: Unable to attach Serviceability Agent. sun.jvm.hotspot.runtime.VM.isCompressedKlassPointersEnabled()
|
||||||
|
Exception in thread "main" java.lang.RuntimeException: Cannot get the field offset, try with -Djol.magicFieldOffset=true
|
||||||
|
at org.openjdk.jol.vm.HotspotUnsafe.fieldOffset(HotspotUnsafe.java:533)
|
||||||
|
at org.openjdk.jol.layouters.CurrentLayouter.layout(CurrentLayouter.java:70)
|
||||||
|
at org.openjdk.jol.info.ClassLayout.parseClass(ClassLayout.java:72)
|
||||||
|
at org.openjdk.jol.info.ClassLayout.parseClass(ClassLayout.java:56)
|
||||||
|
at RecordProbe.main(RecordProbe.java:12)
|
||||||
|
Caused by: java.lang.UnsupportedOperationException: can't get field offset on a record class: private final int RecordProbe$Coordinates.lat
|
||||||
|
at jdk.unsupported/sun.misc.Unsafe.objectFieldOffset(Unsafe.java:903)
|
||||||
|
at org.openjdk.jol.vm.HotspotUnsafe.fieldOffset(HotspotUnsafe.java:526)
|
||||||
|
... 4 more
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# WARNING: Unable to attach Serviceability Agent. sun.jvm.hotspot.runtime.VM.isCompressedKlassPointersEnabled()
|
||||||
|
RecordProbe$Coordinates object internals:
|
||||||
|
OFF SZ TYPE DESCRIPTION VALUE
|
||||||
|
0 8 (object header: mark) N/A
|
||||||
|
8 4 int Coordinates.lat N/A
|
||||||
|
12 4 int Coordinates.lon N/A
|
||||||
|
Instance size: 16 bytes
|
||||||
|
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Instance sizes in bytes, extracted from 10-, 11- and 12- transcripts
|
||||||
|
--- 10-headers-jdk26
|
||||||
|
=== java.lang.Object -> Instance size: 16 bytes
|
||||||
|
=== Point { int x; int y; } -> Instance size: 24 bytes
|
||||||
|
=== Order { long id; int quantity; boolean paid; String customer; } -> Instance size: 32 bytes
|
||||||
|
=== java.lang.Long and java.lang.Integer (boxed values) -> Instance size: 24 bytes
|
||||||
|
=== java.lang.Long and java.lang.Integer (boxed values) -> Instance size: 16 bytes
|
||||||
|
=== int[3] -> Instance size: 32 bytes
|
||||||
|
1,000,000 boxed Integer values in an ArrayList : 20,861,992 bytes
|
||||||
|
1,000,000 boxed Long values in an ArrayList : 28,861,992 bytes
|
||||||
|
1,000,000 Point objects in an ArrayList : 28,861,992 bytes
|
||||||
|
--- 11-headers-jdk27
|
||||||
|
=== java.lang.Object -> Instance size: 8 bytes
|
||||||
|
=== Point { int x; int y; } -> Instance size: 16 bytes
|
||||||
|
=== Order { long id; int quantity; boolean paid; String customer; } -> Instance size: 32 bytes
|
||||||
|
=== java.lang.Long and java.lang.Integer (boxed values) -> Instance size: 16 bytes
|
||||||
|
=== java.lang.Long and java.lang.Integer (boxed values) -> Instance size: 16 bytes
|
||||||
|
=== int[3] -> Instance size: 24 bytes
|
||||||
|
1,000,000 boxed Integer values in an ArrayList : 20,861,984 bytes
|
||||||
|
1,000,000 boxed Long values in an ArrayList : 20,861,984 bytes
|
||||||
|
1,000,000 Point objects in an ArrayList : 20,861,984 bytes
|
||||||
|
--- 12-headers-jdk27-opt-out
|
||||||
|
=== java.lang.Object -> Instance size: 16 bytes
|
||||||
|
=== Point { int x; int y; } -> Instance size: 24 bytes
|
||||||
|
=== Order { long id; int quantity; boolean paid; String customer; } -> Instance size: 32 bytes
|
||||||
|
=== java.lang.Long and java.lang.Integer (boxed values) -> Instance size: 24 bytes
|
||||||
|
=== java.lang.Long and java.lang.Integer (boxed values) -> Instance size: 16 bytes
|
||||||
|
=== int[3] -> Instance size: 32 bytes
|
||||||
|
1,000,000 boxed Integer values in an ArrayList : 20,861,992 bytes
|
||||||
|
1,000,000 boxed Long values in an ArrayList : 28,861,992 bytes
|
||||||
|
1,000,000 Point objects in an ArrayList : 28,861,992 bytes
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
$ javac --enable-preview --release 27 Dominated.java (JDK 27)
|
||||||
|
Dominated.java:9: error: this case label is dominated by a preceding case label
|
||||||
|
case int primitive -> "an int"; // dominated: every Integer is already caught above
|
||||||
|
^
|
||||||
|
1 error
|
||||||
|
exit=1
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
$ javac --enable-preview --release 26 Lazy26.java (JDK 26)
|
||||||
|
exit=0
|
||||||
|
|
||||||
|
$ javac --enable-preview --release 27 Lazy26.java (JDK 27)
|
||||||
|
Lazy26.java:12: error: cannot find symbol
|
||||||
|
if (!GREETING.isInitialized()) {
|
||||||
|
^
|
||||||
|
symbol: method isInitialized()
|
||||||
|
location: variable GREETING of type LazyConstant<String>
|
||||||
|
Lazy26.java:13: error: cannot find symbol
|
||||||
|
System.out.println("not yet: " + GREETING.orElse("<unset>"));
|
||||||
|
^
|
||||||
|
symbol: method orElse(String)
|
||||||
|
location: variable GREETING of type LazyConstant<String>
|
||||||
|
2 errors
|
||||||
|
exit=1
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
$ javac --enable-preview --release 26 Pem26Style.java (JDK 26)
|
||||||
|
exit=0
|
||||||
|
|
||||||
|
$ javac --enable-preview --release 27 Pem26Style.java (JDK 27)
|
||||||
|
Pem26Style.java:1: error: cannot find symbol
|
||||||
|
import java.security.DEREncodable;
|
||||||
|
^
|
||||||
|
symbol: class DEREncodable
|
||||||
|
location: package java.security
|
||||||
|
Pem26Style.java:20: error: cannot find symbol
|
||||||
|
PEMDecoder viaProvider = PEMDecoder.of().withFactory(provider); // 2
|
||||||
|
^
|
||||||
|
symbol: method withFactory(Provider)
|
||||||
|
location: class PEMDecoder
|
||||||
|
Pem26Style.java:22: error: incompatible types: byte[] cannot be converted to String
|
||||||
|
String base64Text = pem.content(); // 3
|
||||||
|
^
|
||||||
|
3 errors
|
||||||
|
exit=1
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
$ javac --enable-preview --release 26 StructuredScope26.java (JDK 26)
|
||||||
|
exit=0
|
||||||
|
|
||||||
|
$ javac --enable-preview --release 27 StructuredScope26.java (JDK 27)
|
||||||
|
StructuredScope26.java:12: error: wrong number of type arguments; required 3
|
||||||
|
try (StructuredTaskScope<String, List<String>> scope =
|
||||||
|
^
|
||||||
|
StructuredScope26.java:17: error: cannot find symbol
|
||||||
|
} catch (StructuredTaskScope.FailedException e) {
|
||||||
|
^
|
||||||
|
symbol: class FailedException
|
||||||
|
location: interface StructuredTaskScope
|
||||||
|
StructuredScope26.java:19: error: cannot find symbol
|
||||||
|
} catch (StructuredTaskScope.TimeoutException e) {
|
||||||
|
^
|
||||||
|
symbol: class TimeoutException
|
||||||
|
location: interface StructuredTaskScope
|
||||||
|
3 errors
|
||||||
|
exit=1
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
$ java --enable-preview StructuredDemo (JDK 27)
|
||||||
|
--- allSuccessfulOrThrow(): results as a List, failures as ExecutionException
|
||||||
|
results = [price=42, stock=7]
|
||||||
|
CHECK ok : join() returns the subtask results in fork order
|
||||||
|
caught java.util.concurrent.ExecutionException with cause java.lang.IllegalStateException: inventory service is down
|
||||||
|
CHECK ok : a failing subtask surfaces as ExecutionException(cause)
|
||||||
|
--- allSuccessfulOrThrow(Function): join() throws the exception type you choose
|
||||||
|
caught OrderFailed: order failed: inventory service is down
|
||||||
|
CHECK ok : the mapped exception wraps the original failure
|
||||||
|
--- withTimeout(...): what happens when the scope runs out of time
|
||||||
|
caught java.util.concurrent.ExecutionException
|
||||||
|
cause: java.util.concurrent.StructuredTaskScope$CancelledByTimeoutException
|
||||||
|
CHECK ok : a timeout is reported as CancelledByTimeoutException
|
||||||
|
exit=0
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
$ java --enable-preview LazyDemo (JDK 27)
|
||||||
|
--- a lazy constant computes once, on first use
|
||||||
|
toString before first get(): jdk.internal.lang.LazyConstantImpl@...[computing function=LazyDemo$$Lambda/0x...@...]
|
||||||
|
(loading settings on main)
|
||||||
|
toString after first get(): jdk.internal.lang.LazyConstantImpl@...[Settings[region=ap-south-1, poolSize=16]]
|
||||||
|
CHECK ok : get() returns the same instance every time
|
||||||
|
CHECK ok : the supplier ran exactly once
|
||||||
|
--- and once even when 64 threads race for it
|
||||||
|
CHECK ok : 64 racing threads triggered exactly one initialisation
|
||||||
|
--- List.ofLazy: each element is computed on first access
|
||||||
|
computed so far: 0
|
||||||
|
squares.get(3) = square(3)=9
|
||||||
|
squares.get(3) = square(3)=9 (second read)
|
||||||
|
CHECK ok : only element 3 was ever computed
|
||||||
|
--- Map.ofLazy: the keys are fixed, the values are computed on first access
|
||||||
|
lengths.get("gamma") = 5
|
||||||
|
CHECK ok : only the requested key's value was computed
|
||||||
|
--- Set.ofLazy (new in 27): membership is decided by a predicate, on demand
|
||||||
|
primes.contains(7) = true, primes.contains(6) = false
|
||||||
|
CHECK ok : the lazy set answers membership using the predicate
|
||||||
|
primes = [2, 3, 5, 7]
|
||||||
|
exit=0
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
$ java --enable-preview PrimitivePatterns (JDK 27)
|
||||||
|
--- instanceof <primitive>: an exact-conversion test, not a cast
|
||||||
|
100 instanceof byte = true
|
||||||
|
300 instanceof byte = false (a byte holds -128..127)
|
||||||
|
CHECK ok : 100 converts to byte exactly, 300 does not
|
||||||
|
binding: byte narrow = 100
|
||||||
|
16_777_216 instanceof float = true
|
||||||
|
16_777_217 instanceof float = false (float has a 24-bit significand)
|
||||||
|
CHECK ok : int to float is exact up to 2^24 and lossy after
|
||||||
|
--- switch on a long, with pattern labels and a guard
|
||||||
|
classify(0) = zero
|
||||||
|
classify(1) = one
|
||||||
|
classify(-7) = negative (-7)
|
||||||
|
classify(9000000000) = positive (9000000000)
|
||||||
|
CHECK ok : long switch dispatches on constants, then patterns
|
||||||
|
--- switch on an Object: an Integer matches 'case int'
|
||||||
|
describe(Integer 7) = an int 7
|
||||||
|
describe(Integer 5000) = a large int 5000
|
||||||
|
describe(Long 7) = a long 7
|
||||||
|
describe(Double 2.5) = a double 2.5
|
||||||
|
describe(String hello) = a String of length 5
|
||||||
|
describe(Character x) = something else: x
|
||||||
|
CHECK ok : an Integer 5000 matches case int with a guard
|
||||||
|
--- record pattern with a narrowing primitive component
|
||||||
|
Reading[celsius=36] -> fits in a byte: 36
|
||||||
|
Reading[celsius=4000] -> needs a full int: 4000
|
||||||
|
CHECK ok : Reading(byte c) matches 36 but not 4000
|
||||||
|
exit=0
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
$ java --enable-preview PemDemo (JDK 27)
|
||||||
|
--- public key, encoded
|
||||||
|
-----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY-----
|
||||||
|
--- private key, encoded
|
||||||
|
-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----
|
||||||
|
CHECK ok : a PublicKey encodes as BEGIN PUBLIC KEY
|
||||||
|
CHECK ok : a PrivateKey encodes as BEGIN PRIVATE KEY (PKCS#8)
|
||||||
|
CHECK ok : the decoded public key equals the original
|
||||||
|
CHECK ok : the decoded private key equals the original
|
||||||
|
--- decode(String) returns whatever the header says it is
|
||||||
|
-----BEGIN PUBLIC KEY----- -> PublicKey (EC)
|
||||||
|
-----BEGIN PRIVATE KEY----- -> PrivateKey (EC)
|
||||||
|
--- encrypted private key
|
||||||
|
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||||
|
CHECK ok : withEncryption produces ENCRYPTED PRIVATE KEY
|
||||||
|
decoded with no password: javax.crypto.EncryptedPrivateKeyInfo
|
||||||
|
CHECK ok : without a password you get the still-encrypted structure back
|
||||||
|
CHECK ok : with the password you get the original private key
|
||||||
|
--- PEM(type, byte[]) does NOT Base64-encode: the bytes go in as they are
|
||||||
|
-----BEGIN ANKURM DEMO-----
|
||||||
|
hello, pem
|
||||||
|
-----END ANKURM DEMO-----
|
||||||
|
CHECK ok : raw bytes appear verbatim between the header and footer
|
||||||
|
--- give it the Base64 text and it round-trips
|
||||||
|
-----BEGIN ANKURM DEMO-----
|
||||||
|
aGVsbG8sIHBlbQ==
|
||||||
|
-----END ANKURM DEMO-----
|
||||||
|
CHECK ok : type, Base64 text (content()) and decoded payload (decode()) all survive
|
||||||
|
exit=0
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
$ java --add-modules jdk.incubator.vector VectorDemo (JDK 27)
|
||||||
|
java.version = 27
|
||||||
|
preferred species = Species[float, 16, S_512_BIT] (16 float lanes, machine dependent)
|
||||||
|
scalar dot = 749999.6
|
||||||
|
vector dot = 749999.6
|
||||||
|
CHECK ok : vector and scalar dot products agree to within 0.1%
|
||||||
|
exit=0
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Who talks to whom: the key-exchange group each pairing actually negotiates (TLS 1.3, loopback)
|
||||||
|
|
||||||
|
=== client JDK 26 -> server JDK 26
|
||||||
|
client offered : "named groups": [x25519, secp256r1, secp384r1, secp521r1, x448, ffdhe2048, ffdhe3072, ffdhe4096, ffdhe6144, ffdhe8192]
|
||||||
|
server selected: x25519
|
||||||
|
handshake complete: TLSv1.3 TLS_AES_256_GCM_SHA384
|
||||||
|
|
||||||
|
=== client JDK 27 -> server JDK 27
|
||||||
|
client offered : "named groups": [X25519MLKEM768, x25519, secp256r1, secp384r1, secp521r1, x448, ffdhe2048, ffdhe3072, ffdhe4096]
|
||||||
|
server selected: X25519MLKEM768
|
||||||
|
handshake complete: TLSv1.3 TLS_AES_256_GCM_SHA384
|
||||||
|
|
||||||
|
=== client JDK 27 -> server JDK 26
|
||||||
|
client offered : "named groups": [X25519MLKEM768, x25519, secp256r1, secp384r1, secp521r1, x448, ffdhe2048, ffdhe3072, ffdhe4096]
|
||||||
|
server selected: x25519
|
||||||
|
handshake complete: TLSv1.3 TLS_AES_256_GCM_SHA384
|
||||||
|
|
||||||
|
=== client JDK 26 -> server JDK 27
|
||||||
|
client offered : "named groups": [x25519, secp256r1, secp384r1, secp521r1, x448, ffdhe2048, ffdhe3072, ffdhe4096, ffdhe6144, ffdhe8192]
|
||||||
|
server selected: x25519
|
||||||
|
handshake complete: TLSv1.3 TLS_AES_256_GCM_SHA384
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Size of the first TLS record (the ClientHello) each JDK sends
|
||||||
|
|
||||||
|
=== JDK 26
|
||||||
|
java.version = 26.0.2.1
|
||||||
|
record type = 0x16 (0x16 = handshake)
|
||||||
|
ClientHello record = 417 bytes (+5 byte record header)
|
||||||
|
fits one TCP segment = yes (1460-byte MSS on a 1500-byte MTU)
|
||||||
|
|
||||||
|
=== JDK 27
|
||||||
|
java.version = 27
|
||||||
|
record type = 0x16 (0x16 = handshake)
|
||||||
|
ClientHello record = 1573 bytes (+5 byte record header)
|
||||||
|
fits one TCP segment = NO (1460-byte MSS on a 1500-byte MTU)
|
||||||
|
|
||||||
|
=== JDK 27 with -Djdk.tls.namedGroups=x25519,secp256r1 (post-quantum group removed)
|
||||||
|
java.version = 27
|
||||||
|
record type = 0x16 (0x16 = handshake)
|
||||||
|
ClientHello record = 408 bytes (+5 byte record header)
|
||||||
|
fits one TCP segment = yes (1460-byte MSS on a 1500-byte MTU)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Opting out on JDK 27: a client that does not offer the hybrid group
|
||||||
|
|
||||||
|
=== client JDK 27 with -Djdk.tls.namedGroups=x25519,secp256r1 -> server JDK 27
|
||||||
|
client offered : "named groups": [x25519, secp256r1]
|
||||||
|
server selected: x25519
|
||||||
|
handshake complete: TLSv1.3 TLS_AES_256_GCM_SHA384
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# The process under test: java -Dapi.token=abc123 -Dregion=ap-south-1 ... Idle --password=hunter2 --user=ankur
|
||||||
|
# with DB_PASSWORD=hunter2 in the environment. Which of those values reach the recording?
|
||||||
|
|
||||||
|
=== jdk26-default
|
||||||
|
jvmArguments = "-Dapi.token=abc123 -Dregion=ap-south-1 -XX:StartFlightRecording:filename=<file>"
|
||||||
|
javaArguments = "Idle --password=hunter2 --user=ankur"
|
||||||
|
key = "region" value = "ap-south-1"
|
||||||
|
key = "api.token" value = "abc123"
|
||||||
|
key = "DB_PASSWORD" value = "hunter2"
|
||||||
|
|
||||||
|
=== jdk27-default
|
||||||
|
jvmArguments = "[REDACTED] -Dregion=ap-south-1 -XX:StartFlightRecording:filename=<file>"
|
||||||
|
javaArguments = "Idle [REDACTED] --user=ankur"
|
||||||
|
key = "region" value = "ap-south-1"
|
||||||
|
key = "api.token" value = "[REDACTED]"
|
||||||
|
key = "DB_PASSWORD" value = "[REDACTED]"
|
||||||
|
|
||||||
|
=== jdk27-own-list-replaces-defaults
|
||||||
|
-XX:FlightRecorderOptions:redact-argument=--user*,redact-key=region
|
||||||
|
jvmArguments = "-Dapi.token=abc123 -Dregion=[REDACTED] -XX:FlightRecorderOptions:redact-argument=[REDACTED],redact-key=region -XX:StartFlightRecording:filename=<file>"
|
||||||
|
javaArguments = "Idle --password=hunter2 [REDACTED]"
|
||||||
|
key = "region" value = "[REDACTED]"
|
||||||
|
key = "api.token" value = "abc123"
|
||||||
|
key = "DB_PASSWORD" value = "hunter2"
|
||||||
|
|
||||||
|
=== jdk27-plus-extends-defaults
|
||||||
|
-XX:FlightRecorderOptions:redact-argument=+--user*,redact-key=+region
|
||||||
|
jvmArguments = "[REDACTED] -Dregion=[REDACTED] -XX:FlightRecorderOptions:redact-argument=[REDACTED],redact-key=+region -XX:StartFlightRecording:filename=<file>"
|
||||||
|
javaArguments = "Idle [REDACTED] [REDACTED]"
|
||||||
|
key = "region" value = "[REDACTED]"
|
||||||
|
key = "api.token" value = "[REDACTED]"
|
||||||
|
key = "DB_PASSWORD" value = "[REDACTED]"
|
||||||
|
|
||||||
|
=== jdk27-none-disables-redaction
|
||||||
|
-XX:FlightRecorderOptions:redact-argument=none,redact-key=none
|
||||||
|
jvmArguments = "-Dapi.token=abc123 -Dregion=ap-south-1 -XX:FlightRecorderOptions:redact-argument=[REDACTED],redact-key=none -XX:StartFlightRecording:filename=<file>"
|
||||||
|
javaArguments = "Idle --password=hunter2 --user=ankur"
|
||||||
|
key = "region" value = "ap-south-1"
|
||||||
|
key = "api.token" value = "abc123"
|
||||||
|
key = "DB_PASSWORD" value = "hunter2"
|
||||||
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# jdk.incubator.vector: what changed between 26 and 27 (JEP 529 was the 11th incubation in 26, JEP 537 is the 12th in 27)
|
||||||
|
|
||||||
|
## public classes removed
|
||||||
|
## public classes added
|
||||||
|
## public class changes
|
||||||
|
jdk.incubator.vector.VectorOperators
|
||||||
|
- public abstract class jdk.incubator.vector.VectorOperators {
|
||||||
|
+ public final class jdk.incubator.vector.VectorOperators {
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
# Public API removed/added/changed between JDK 26 and 27 (javap -public of every exported class; internal packages skipped)
|
||||||
|
|
||||||
|
## public classes removed
|
||||||
|
java.security.DEREncodable
|
||||||
|
java.util.concurrent.StructuredTaskScope$FailedException
|
||||||
|
java.util.concurrent.StructuredTaskScope$TimeoutException
|
||||||
|
## public classes added
|
||||||
|
com.sun.source.tree.VarTypeTree
|
||||||
|
java.security.BinaryEncodable
|
||||||
|
java.time.format.DateTimeFormatterPatternProvider
|
||||||
|
java.util.concurrent.StructuredTaskScope$CancelledByTimeoutException
|
||||||
|
javax.crypto.CryptoException
|
||||||
|
## public class changes
|
||||||
|
com.sun.source.tree.Tree$Kind
|
||||||
|
+ public static final com.sun.source.tree.Tree$Kind VAR_TYPE;
|
||||||
|
com.sun.source.tree.TreeVisitor
|
||||||
|
+ public abstract R visitVarType(com.sun.source.tree.VarTypeTree, P);
|
||||||
|
com.sun.source.util.DocSourcePositions
|
||||||
|
- public abstract long getEndPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.doctree.DocCommentTree, com.sun.source.doctree.DocTree);
|
||||||
|
- public abstract long getStartPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.doctree.DocCommentTree, com.sun.source.doctree.DocTree);
|
||||||
|
+ public abstract long getEndPosition(com.sun.source.doctree.DocCommentTree, com.sun.source.doctree.DocTree);
|
||||||
|
+ public abstract long getStartPosition(com.sun.source.doctree.DocCommentTree, com.sun.source.doctree.DocTree);
|
||||||
|
+ public default long getEndPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.doctree.DocCommentTree, com.sun.source.doctree.DocTree);
|
||||||
|
+ public default long getStartPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.doctree.DocCommentTree, com.sun.source.doctree.DocTree);
|
||||||
|
com.sun.source.util.SimpleTreeVisitor
|
||||||
|
+ public R visitVarType(com.sun.source.tree.VarTypeTree, P);
|
||||||
|
com.sun.source.util.SourcePositions
|
||||||
|
- public abstract long getEndPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.tree.Tree);
|
||||||
|
- public abstract long getStartPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.tree.Tree);
|
||||||
|
+ public abstract long getEndPosition(com.sun.source.tree.Tree);
|
||||||
|
+ public abstract long getStartPosition(com.sun.source.tree.Tree);
|
||||||
|
+ public default long getEndPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.tree.Tree);
|
||||||
|
+ public default long getStartPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.tree.Tree);
|
||||||
|
com.sun.source.util.TreeScanner
|
||||||
|
+ public R visitVarType(com.sun.source.tree.VarTypeTree, P);
|
||||||
|
java.awt.KeyboardFocusManager
|
||||||
|
- public static java.awt.KeyboardFocusManager getCurrentKeyboardFocusManager();
|
||||||
|
+ public static synchronized java.awt.KeyboardFocusManager getCurrentKeyboardFocusManager();
|
||||||
|
java.awt.geom.Arc2D
|
||||||
|
+ public java.awt.Rectangle getBounds();
|
||||||
|
java.lang.LazyConstant
|
||||||
|
- public abstract T orElse(T);
|
||||||
|
- public abstract boolean isInitialized();
|
||||||
|
java.lang.Math
|
||||||
|
+ public static double acosh(double);
|
||||||
|
+ public static double asinh(double);
|
||||||
|
+ public static double atanh(double);
|
||||||
|
java.lang.StrictMath
|
||||||
|
+ public static double acosh(double);
|
||||||
|
+ public static double asinh(double);
|
||||||
|
+ public static double atanh(double);
|
||||||
|
java.lang.String
|
||||||
|
+ public int encodedLength(java.nio.charset.Charset);
|
||||||
|
java.lang.classfile.ClassFile
|
||||||
|
+ public static final int JAVA_27_VERSION;
|
||||||
|
java.lang.classfile.instruction.IncrementInstruction
|
||||||
|
+ public static java.lang.classfile.instruction.IncrementInstruction of(java.lang.classfile.Opcode, int, int);
|
||||||
|
java.lang.foreign.MemorySegment
|
||||||
|
+ public abstract java.lang.String getString(long, java.nio.charset.Charset, long);
|
||||||
|
+ public static long copy(java.lang.String, java.nio.charset.Charset, int, java.lang.foreign.MemorySegment, long, int);
|
||||||
|
java.lang.foreign.SegmentAllocator
|
||||||
|
+ public default java.lang.foreign.MemorySegment allocateFrom(java.lang.String, java.nio.charset.Charset, int, int);
|
||||||
|
java.lang.reflect.ClassFileFormatVersion
|
||||||
|
+ public static final java.lang.reflect.ClassFileFormatVersion RELEASE_27;
|
||||||
|
java.lang.reflect.Executable
|
||||||
|
- public abstract java.lang.String toGenericString();
|
||||||
|
+ public java.lang.String toGenericString();
|
||||||
|
java.math.BigDecimal
|
||||||
|
+ public java.math.BigDecimal rootn(int, java.math.MathContext);
|
||||||
|
java.security.AsymmetricKey
|
||||||
|
- public interface java.security.AsymmetricKey extends java.security.Key,java.security.DEREncodable {
|
||||||
|
+ public interface java.security.AsymmetricKey extends java.security.Key,java.security.BinaryEncodable {
|
||||||
|
java.security.KeyPair
|
||||||
|
- public final class java.security.KeyPair implements java.io.Serializable,java.security.DEREncodable {
|
||||||
|
+ public final class java.security.KeyPair implements java.io.Serializable,java.security.BinaryEncodable {
|
||||||
|
java.security.KeyStore
|
||||||
|
+ public final java.time.Instant getCreationInstant(java.lang.String) throws java.security.KeyStoreException;
|
||||||
|
java.security.KeyStoreSpi
|
||||||
|
+ public java.time.Instant engineGetCreationInstant(java.lang.String);
|
||||||
|
java.security.PEM
|
||||||
|
- public final class java.security.PEM extends java.lang.Record implements java.security.DEREncodable {
|
||||||
|
+ public final class java.security.PEM implements java.security.BinaryEncodable {
|
||||||
|
- public final boolean equals(java.lang.Object);
|
||||||
|
- public final byte[] decode();
|
||||||
|
- public final int hashCode();
|
||||||
|
- public final java.lang.String toString();
|
||||||
|
- public java.lang.String content();
|
||||||
|
+ public byte[] content();
|
||||||
|
+ public byte[] decode();
|
||||||
|
+ public java.lang.String toString();
|
||||||
|
+ public java.security.PEM(java.lang.String, byte[]);
|
||||||
|
+ public java.security.PEM(java.lang.String, byte[], byte[]);
|
||||||
|
java.security.PEMDecoder
|
||||||
|
- public <S extends java.security.DEREncodable> S decode(java.io.InputStream, java.lang.Class<S>) throws java.io.IOException;
|
||||||
|
- public <S extends java.security.DEREncodable> S decode(java.lang.String, java.lang.Class<S>);
|
||||||
|
- public java.security.DEREncodable decode(java.io.InputStream) throws java.io.IOException;
|
||||||
|
- public java.security.DEREncodable decode(java.lang.String);
|
||||||
|
- public java.security.PEMDecoder withFactory(java.security.Provider);
|
||||||
|
+ public <S extends java.security.BinaryEncodable> S decode(java.io.InputStream, java.lang.Class<S>) throws java.io.IOException;
|
||||||
|
+ public <S extends java.security.BinaryEncodable> S decode(java.lang.String, java.lang.Class<S>);
|
||||||
|
+ public java.security.BinaryEncodable decode(java.io.InputStream) throws java.io.IOException;
|
||||||
|
+ public java.security.BinaryEncodable decode(java.lang.String);
|
||||||
|
+ public java.security.PEMDecoder withFactoriesOf(java.security.Provider);
|
||||||
|
java.security.PEMEncoder
|
||||||
|
- public byte[] encode(java.security.DEREncodable);
|
||||||
|
- public java.lang.String encodeToString(java.security.DEREncodable);
|
||||||
|
+ public byte[] encode(java.security.BinaryEncodable);
|
||||||
|
+ public java.lang.String encodeToString(java.security.BinaryEncodable);
|
||||||
|
java.security.cert.X509CRL
|
||||||
|
- public abstract class java.security.cert.X509CRL extends java.security.cert.CRL implements java.security.cert.X509Extension,java.security.DEREncodable {
|
||||||
|
+ public abstract class java.security.cert.X509CRL extends java.security.cert.CRL implements java.security.cert.X509Extension,java.security.BinaryEncodable {
|
||||||
|
java.security.cert.X509Certificate
|
||||||
|
- public abstract class java.security.cert.X509Certificate extends java.security.cert.Certificate implements java.security.cert.X509Extension,java.security.DEREncodable {
|
||||||
|
+ public abstract class java.security.cert.X509Certificate extends java.security.cert.Certificate implements java.security.cert.X509Extension,java.security.BinaryEncodable {
|
||||||
|
java.security.spec.PKCS8EncodedKeySpec
|
||||||
|
- public class java.security.spec.PKCS8EncodedKeySpec extends java.security.spec.EncodedKeySpec implements java.security.DEREncodable {
|
||||||
|
+ public class java.security.spec.PKCS8EncodedKeySpec extends java.security.spec.EncodedKeySpec implements java.security.BinaryEncodable {
|
||||||
|
java.security.spec.X509EncodedKeySpec
|
||||||
|
- public class java.security.spec.X509EncodedKeySpec extends java.security.spec.EncodedKeySpec implements java.security.DEREncodable {
|
||||||
|
+ public class java.security.spec.X509EncodedKeySpec extends java.security.spec.EncodedKeySpec implements java.security.BinaryEncodable {
|
||||||
|
java.util.EnumSet
|
||||||
|
+ public java.util.Spliterator<E> spliterator();
|
||||||
|
java.util.Locale
|
||||||
|
- public final java.lang.String getDisplayCountry();
|
||||||
|
- public final java.lang.String getDisplayLanguage();
|
||||||
|
- public final java.lang.String getDisplayName();
|
||||||
|
- public final java.lang.String getDisplayVariant();
|
||||||
|
+ public java.lang.String getDisplayCountry();
|
||||||
|
+ public java.lang.String getDisplayLanguage();
|
||||||
|
+ public java.lang.String getDisplayName();
|
||||||
|
+ public java.lang.String getDisplayVariant();
|
||||||
|
java.util.Set
|
||||||
|
+ public static <E> java.util.Set<E> ofLazy(java.util.Set<? extends E>, java.util.function.Predicate<? super E>);
|
||||||
|
java.util.concurrent.StructuredTaskScope
|
||||||
|
- public interface java.util.concurrent.StructuredTaskScope<T, R> extends java.lang.AutoCloseable {
|
||||||
|
+ public interface java.util.concurrent.StructuredTaskScope<T, R, R_X extends java.lang.Throwable> extends java.lang.AutoCloseable {
|
||||||
|
- public abstract R join() throws java.lang.InterruptedException;
|
||||||
|
- public static <T, R> java.util.concurrent.StructuredTaskScope<T, R> open(java.util.concurrent.StructuredTaskScope$Joiner<? super T, ? extends R>);
|
||||||
|
- public static <T, R> java.util.concurrent.StructuredTaskScope<T, R> open(java.util.concurrent.StructuredTaskScope$Joiner<? super T, ? extends R>, java.util.function.UnaryOperator<java.util.concurrent.StructuredTaskScope$Configuration>);
|
||||||
|
- public static <T> java.util.concurrent.StructuredTaskScope<T, java.lang.Void> open();
|
||||||
|
+ public abstract R join() throws R_X, java.lang.InterruptedException;
|
||||||
|
+ public static <T, R, R_X extends java.lang.Throwable> java.util.concurrent.StructuredTaskScope<T, R, R_X> open(java.util.concurrent.StructuredTaskScope$Joiner<? super T, ? extends R, R_X>);
|
||||||
|
+ public static <T, R, R_X extends java.lang.Throwable> java.util.concurrent.StructuredTaskScope<T, R, R_X> open(java.util.concurrent.StructuredTaskScope$Joiner<? super T, ? extends R, R_X>, java.util.function.UnaryOperator<java.util.concurrent.StructuredTaskScope$Configuration>);
|
||||||
|
+ public static <T> java.util.concurrent.StructuredTaskScope<T, java.lang.Void, java.util.concurrent.ExecutionException> open();
|
||||||
|
+ public static <T> java.util.concurrent.StructuredTaskScope<T, java.lang.Void, java.util.concurrent.ExecutionException> open(java.util.function.UnaryOperator<java.util.concurrent.StructuredTaskScope$Configuration>);
|
||||||
|
java.util.concurrent.StructuredTaskScope$Joiner
|
||||||
|
- public interface java.util.concurrent.StructuredTaskScope$Joiner<T, R> {
|
||||||
|
+ public interface java.util.concurrent.StructuredTaskScope$Joiner<T, R, R_X extends java.lang.Throwable> {
|
||||||
|
- public abstract R result() throws java.lang.Throwable;
|
||||||
|
- public default void onTimeout();
|
||||||
|
- public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, T> anySuccessfulOrThrow();
|
||||||
|
- public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.lang.Void> awaitAll();
|
||||||
|
- public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.lang.Void> awaitAllSuccessfulOrThrow();
|
||||||
|
- public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.List<T>> allSuccessfulOrThrow();
|
||||||
|
- public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.List<java.util.concurrent.StructuredTaskScope$Subtask<T>>> allUntil(java.util.function.Predicate<java.util.concurrent.StructuredTaskScope$Subtask<T>>);
|
||||||
|
+ public abstract R result() throws R_X;
|
||||||
|
+ public abstract R timeout() throws R_X;
|
||||||
|
+ public static <T, R_X extends java.lang.Throwable> java.util.concurrent.StructuredTaskScope$Joiner<T, T, R_X> anySuccessfulOrThrow(java.util.function.Function<java.lang.Throwable, R_X>);
|
||||||
|
+ public static <T, R_X extends java.lang.Throwable> java.util.concurrent.StructuredTaskScope$Joiner<T, java.lang.Void, R_X> awaitAllSuccessfulOrThrow(java.util.function.Function<java.lang.Throwable, R_X>);
|
||||||
|
+ public static <T, R_X extends java.lang.Throwable> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.List<T>, R_X> allSuccessfulOrThrow(java.util.function.Function<java.lang.Throwable, R_X>);
|
||||||
|
+ public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, T, java.util.concurrent.ExecutionException> anySuccessfulOrThrow();
|
||||||
|
+ public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.lang.Void, java.util.concurrent.ExecutionException> awaitAllSuccessfulOrThrow();
|
||||||
|
+ public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.List<T>, java.util.concurrent.ExecutionException> allSuccessfulOrThrow();
|
||||||
|
+ public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.List<java.util.concurrent.StructuredTaskScope$Subtask<T>>, java.lang.RuntimeException> allUntil(java.util.function.Predicate<? super java.util.concurrent.StructuredTaskScope$Subtask<T>>);
|
||||||
|
javax.crypto.EncryptedPrivateKeyInfo
|
||||||
|
- public class javax.crypto.EncryptedPrivateKeyInfo implements java.security.DEREncodable {
|
||||||
|
+ public class javax.crypto.EncryptedPrivateKeyInfo implements java.security.BinaryEncodable {
|
||||||
|
- public java.security.KeyPair getKeyPair(java.security.Key, java.security.Provider) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException;
|
||||||
|
- public java.security.PrivateKey getKey(java.security.Key, java.security.Provider) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException;
|
||||||
|
- public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.DEREncodable, char[]);
|
||||||
|
- public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.DEREncodable, char[], java.lang.String, java.security.spec.AlgorithmParameterSpec, java.security.Provider);
|
||||||
|
- public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.DEREncodable, java.security.Key, java.lang.String, java.security.spec.AlgorithmParameterSpec, java.security.Provider, java.security.SecureRandom);
|
||||||
|
+ public java.security.KeyPair getKeyPair(java.security.Key) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException;
|
||||||
|
+ public java.security.PrivateKey getKey(java.security.Key) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException;
|
||||||
|
+ public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.BinaryEncodable, char[]);
|
||||||
|
+ public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.BinaryEncodable, char[], java.lang.String, java.security.spec.AlgorithmParameterSpec, java.security.Provider);
|
||||||
|
+ public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.BinaryEncodable, java.security.Key, java.lang.String, java.security.spec.AlgorithmParameterSpec, java.security.Provider, java.security.SecureRandom);
|
||||||
|
javax.lang.model.SourceVersion
|
||||||
|
+ public static final javax.lang.model.SourceVersion RELEASE_27;
|
||||||
|
javax.swing.JEditorPane$JEditorPaneAccessibleHypertextSupport
|
||||||
|
+ public void changedUpdate(javax.swing.event.DocumentEvent);
|
||||||
|
+ public void insertUpdate(javax.swing.event.DocumentEvent);
|
||||||
|
+ public void removeUpdate(javax.swing.event.DocumentEvent);
|
||||||
|
jdk.incubator.vector.VectorOperators
|
||||||
|
- public abstract class jdk.incubator.vector.VectorOperators {
|
||||||
|
+ public final class jdk.incubator.vector.VectorOperators {
|
||||||
|
jdk.javadoc.doclet.Taglet
|
||||||
|
+ public default java.lang.String toString(java.util.List<? extends com.sun.source.doctree.DocTree>, javax.lang.model.element.Element, java.net.URI);
|
||||||
|
jdk.jfr.events.ContainerMemoryUsageEvent
|
||||||
|
+ public long hostMemoryUsage;
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
# Public API removed/added/changed between JDK 25 and 26
|
||||||
|
|
||||||
|
## public classes removed
|
||||||
|
java.applet.Applet
|
||||||
|
java.applet.Applet$AccessibleApplet
|
||||||
|
java.applet.AppletContext
|
||||||
|
java.applet.AppletStub
|
||||||
|
java.applet.AudioClip
|
||||||
|
java.beans.AppletInitializer
|
||||||
|
java.lang.StableValue
|
||||||
|
java.security.PEMRecord
|
||||||
|
javax.management.modelmbean.XMLParseException
|
||||||
|
javax.swing.JApplet
|
||||||
|
javax.swing.JApplet$AccessibleJApplet
|
||||||
|
## public classes added
|
||||||
|
java.lang.LazyConstant
|
||||||
|
java.net.http.HttpOption
|
||||||
|
java.net.http.HttpOption$Http3DiscoveryMode
|
||||||
|
java.net.http.HttpResponse$PushPromiseHandler$PushId
|
||||||
|
java.net.http.HttpResponse$PushPromiseHandler$PushId$Http3PushId
|
||||||
|
java.net.http.StreamLimitException
|
||||||
|
java.net.http.UnsupportedProtocolVersionException
|
||||||
|
java.security.PEM
|
||||||
|
javax.crypto.spec.HPKEParameterSpec
|
||||||
|
jdk.jfr.events.FinalFieldMutationEvent
|
||||||
|
jdk.jshell.SourceCodeAnalysis$CompletionContext
|
||||||
|
jdk.jshell.SourceCodeAnalysis$CompletionState
|
||||||
|
jdk.jshell.SourceCodeAnalysis$ElementSuggestion
|
||||||
|
jdk.jshell.SourceCodeAnalysis$ElementSuggestionConvertor
|
||||||
|
## public class changes
|
||||||
|
com.sun.net.httpserver.HttpExchange
|
||||||
|
+ public static final long RSPBODY_CHUNKED;
|
||||||
|
+ public static final long RSPBODY_EMPTY;
|
||||||
|
java.awt.Robot
|
||||||
|
+ public static final int DEFAULT_DELAY;
|
||||||
|
+ public static final int DEFAULT_STEP_LENGTH;
|
||||||
|
+ public synchronized void type(char);
|
||||||
|
+ public synchronized void type(int);
|
||||||
|
+ public void click();
|
||||||
|
+ public void click(int);
|
||||||
|
+ public void glide(int, int);
|
||||||
|
+ public void glide(int, int, int, int);
|
||||||
|
+ public void glide(int, int, int, int, int, int);
|
||||||
|
+ public void waitForIdle(int);
|
||||||
|
java.beans.Beans
|
||||||
|
- public static java.lang.Object instantiate(java.lang.ClassLoader, java.lang.String, java.beans.beancontext.BeanContext, java.beans.AppletInitializer) throws java.io.IOException, java.lang.ClassNotFoundException;
|
||||||
|
java.lang.Character$UnicodeBlock
|
||||||
|
+ public static final java.lang.Character$UnicodeBlock BERIA_ERFE;
|
||||||
|
+ public static final java.lang.Character$UnicodeBlock CJK_UNIFIED_IDEOGRAPHS_EXTENSION_J;
|
||||||
|
+ public static final java.lang.Character$UnicodeBlock MISCELLANEOUS_SYMBOLS_SUPPLEMENT;
|
||||||
|
+ public static final java.lang.Character$UnicodeBlock SHARADA_SUPPLEMENT;
|
||||||
|
+ public static final java.lang.Character$UnicodeBlock SIDETIC;
|
||||||
|
+ public static final java.lang.Character$UnicodeBlock TAI_YO;
|
||||||
|
+ public static final java.lang.Character$UnicodeBlock TANGUT_COMPONENTS_SUPPLEMENT;
|
||||||
|
+ public static final java.lang.Character$UnicodeBlock TOLONG_SIKI;
|
||||||
|
java.lang.Character$UnicodeScript
|
||||||
|
+ public static final java.lang.Character$UnicodeScript BERIA_ERFE;
|
||||||
|
+ public static final java.lang.Character$UnicodeScript SIDETIC;
|
||||||
|
+ public static final java.lang.Character$UnicodeScript TAI_YO;
|
||||||
|
+ public static final java.lang.Character$UnicodeScript TOLONG_SIKI;
|
||||||
|
java.lang.Process
|
||||||
|
- public abstract class java.lang.Process {
|
||||||
|
+ public abstract class java.lang.Process implements java.io.Closeable {
|
||||||
|
+ public void close() throws java.io.IOException;
|
||||||
|
java.lang.String
|
||||||
|
+ public boolean equalsFoldCase(java.lang.String);
|
||||||
|
+ public int compareToFoldCase(java.lang.String);
|
||||||
|
+ public static final java.util.Comparator<java.lang.String> UNICODE_CASEFOLD_ORDER;
|
||||||
|
java.lang.Thread
|
||||||
|
- public final void stop();
|
||||||
|
java.lang.classfile.ClassFile
|
||||||
|
+ public static final int JAVA_26_VERSION;
|
||||||
|
java.lang.management.MemoryMXBean
|
||||||
|
+ public default long getTotalGcCpuTime();
|
||||||
|
java.lang.reflect.ClassFileFormatVersion
|
||||||
|
+ public static final java.lang.reflect.ClassFileFormatVersion RELEASE_26;
|
||||||
|
java.math.BigInteger
|
||||||
|
+ public java.math.BigInteger rootn(int);
|
||||||
|
+ public java.math.BigInteger[] rootnAndRemainder(int);
|
||||||
|
java.net.MulticastSocket
|
||||||
|
- public byte getTTL() throws java.io.IOException;
|
||||||
|
- public void send(java.net.DatagramPacket, byte) throws java.io.IOException;
|
||||||
|
- public void setTTL(byte) throws java.io.IOException;
|
||||||
|
java.net.http.HttpClient$Version
|
||||||
|
+ public static final java.net.http.HttpClient$Version HTTP_3;
|
||||||
|
java.net.http.HttpRequest
|
||||||
|
+ public <T> java.util.Optional<T> getOption(java.net.http.HttpOption<T>);
|
||||||
|
java.net.http.HttpRequest$BodyPublishers
|
||||||
|
+ public static java.net.http.HttpRequest$BodyPublisher ofFileChannel(java.nio.channels.FileChannel, long, long) throws java.io.IOException;
|
||||||
|
java.net.http.HttpRequest$Builder
|
||||||
|
+ public default <T> java.net.http.HttpRequest$Builder setOption(java.net.http.HttpOption<T>, T);
|
||||||
|
java.net.http.HttpResponse$PushPromiseHandler
|
||||||
|
+ public default void applyPushPromise(java.net.http.HttpRequest, java.net.http.HttpRequest, java.net.http.HttpResponse$PushPromiseHandler$PushId, java.util.function.Function<java.net.http.HttpResponse$BodyHandler<T>, java.util.concurrent.CompletableFuture<java.net.http.HttpResponse<T>>>);
|
||||||
|
+ public default void notifyAdditionalPromise(java.net.http.HttpRequest, java.net.http.HttpResponse$PushPromiseHandler$PushId);
|
||||||
|
java.nio.ByteOrder
|
||||||
|
- public final class java.nio.ByteOrder {
|
||||||
|
+ public final class java.nio.ByteOrder extends java.lang.Enum<java.nio.ByteOrder> {
|
||||||
|
- public java.lang.String toString();
|
||||||
|
+ public static java.nio.ByteOrder valueOf(java.lang.String);
|
||||||
|
+ public static java.nio.ByteOrder[] values();
|
||||||
|
java.security.SecureRandom
|
||||||
|
+ public long nextLong();
|
||||||
|
java.sql.Array
|
||||||
|
- public interface java.sql.Array {
|
||||||
|
+ public interface java.sql.Array extends java.lang.AutoCloseable {
|
||||||
|
+ public default void close() throws java.sql.SQLException;
|
||||||
|
java.sql.Blob
|
||||||
|
- public interface java.sql.Blob {
|
||||||
|
+ public interface java.sql.Blob extends java.lang.AutoCloseable {
|
||||||
|
+ public default void close() throws java.sql.SQLException;
|
||||||
|
java.sql.Clob
|
||||||
|
- public interface java.sql.Clob {
|
||||||
|
+ public interface java.sql.Clob extends java.lang.AutoCloseable {
|
||||||
|
+ public default void close() throws java.sql.SQLException;
|
||||||
|
java.sql.Connection
|
||||||
|
+ public default boolean isSimpleIdentifier(java.lang.String) throws java.sql.SQLException;
|
||||||
|
+ public default java.lang.String enquoteIdentifier(java.lang.String, boolean) throws java.sql.SQLException;
|
||||||
|
+ public default java.lang.String enquoteLiteral(java.lang.String) throws java.sql.SQLException;
|
||||||
|
+ public default java.lang.String enquoteNCharLiteral(java.lang.String) throws java.sql.SQLException;
|
||||||
|
java.sql.JDBCType
|
||||||
|
+ public static final java.sql.JDBCType DECFLOAT;
|
||||||
|
+ public static final java.sql.JDBCType JSON;
|
||||||
|
java.sql.SQLXML
|
||||||
|
- public interface java.sql.SQLXML {
|
||||||
|
+ public interface java.sql.SQLXML extends java.lang.AutoCloseable {
|
||||||
|
+ public default void close() throws java.sql.SQLException;
|
||||||
|
java.sql.Types
|
||||||
|
+ public static final int DECFLOAT;
|
||||||
|
+ public static final int JSON;
|
||||||
|
java.time.Duration
|
||||||
|
+ public static final java.time.Duration MAX;
|
||||||
|
+ public static final java.time.Duration MIN;
|
||||||
|
java.time.Instant
|
||||||
|
+ public java.time.Instant plusSaturating(java.time.Duration);
|
||||||
|
java.util.Comparator
|
||||||
|
+ public default <U extends T> U max(U, U);
|
||||||
|
+ public default <U extends T> U min(U, U);
|
||||||
|
java.util.List
|
||||||
|
+ public static <E> java.util.List<E> ofLazy(int, java.util.function.IntFunction<? extends E>);
|
||||||
|
java.util.Locale$IsoCountryCode
|
||||||
|
- public abstract class java.util.Locale$IsoCountryCode extends java.lang.Enum<java.util.Locale$IsoCountryCode> {
|
||||||
|
+ public final class java.util.Locale$IsoCountryCode extends java.lang.Enum<java.util.Locale$IsoCountryCode> {
|
||||||
|
java.util.Map
|
||||||
|
+ public static <K, V> java.util.Map<K, V> ofLazy(java.util.Set<? extends K>, java.util.function.Function<? super K, ? extends V>);
|
||||||
|
java.util.UUID
|
||||||
|
+ public static java.util.UUID ofEpochMillis(long);
|
||||||
|
java.util.concurrent.StructuredTaskScope
|
||||||
|
- public static <T, R> java.util.concurrent.StructuredTaskScope<T, R> open(java.util.concurrent.StructuredTaskScope$Joiner<? super T, ? extends R>, java.util.function.Function<java.util.concurrent.StructuredTaskScope$Configuration, java.util.concurrent.StructuredTaskScope$Configuration>);
|
||||||
|
+ public static <T, R> java.util.concurrent.StructuredTaskScope<T, R> open(java.util.concurrent.StructuredTaskScope$Joiner<? super T, ? extends R>, java.util.function.UnaryOperator<java.util.concurrent.StructuredTaskScope$Configuration>);
|
||||||
|
java.util.concurrent.StructuredTaskScope$Joiner
|
||||||
|
- public default boolean onComplete(java.util.concurrent.StructuredTaskScope$Subtask<? extends T>);
|
||||||
|
- public default boolean onFork(java.util.concurrent.StructuredTaskScope$Subtask<? extends T>);
|
||||||
|
- public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, T> anySuccessfulResultOrThrow();
|
||||||
|
- public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.stream.Stream<java.util.concurrent.StructuredTaskScope$Subtask<T>>> allSuccessfulOrThrow();
|
||||||
|
- public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.stream.Stream<java.util.concurrent.StructuredTaskScope$Subtask<T>>> allUntil(java.util.function.Predicate<java.util.concurrent.StructuredTaskScope$Subtask<? extends T>>);
|
||||||
|
+ public default boolean onComplete(java.util.concurrent.StructuredTaskScope$Subtask<T>);
|
||||||
|
+ public default boolean onFork(java.util.concurrent.StructuredTaskScope$Subtask<T>);
|
||||||
|
+ public default void onTimeout();
|
||||||
|
+ public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, T> anySuccessfulOrThrow();
|
||||||
|
+ public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.List<T>> allSuccessfulOrThrow();
|
||||||
|
+ public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.List<java.util.concurrent.StructuredTaskScope$Subtask<T>>> allUntil(java.util.function.Predicate<java.util.concurrent.StructuredTaskScope$Subtask<T>>);
|
||||||
|
java.util.concurrent.ThreadLocalRandom
|
||||||
|
+ public double nextGaussian();
|
||||||
|
javax.crypto.EncryptedPrivateKeyInfo
|
||||||
|
- public java.security.PrivateKey getKey(char[]) throws java.security.GeneralSecurityException;
|
||||||
|
- public java.security.PrivateKey getKey(java.security.Key, java.security.Provider) throws java.security.GeneralSecurityException;
|
||||||
|
- public static javax.crypto.EncryptedPrivateKeyInfo encryptKey(java.security.PrivateKey, char[]);
|
||||||
|
- public static javax.crypto.EncryptedPrivateKeyInfo encryptKey(java.security.PrivateKey, char[], java.lang.String, java.security.spec.AlgorithmParameterSpec, java.security.Provider);
|
||||||
|
- public static javax.crypto.EncryptedPrivateKeyInfo encryptKey(java.security.PrivateKey, java.security.Key, java.lang.String, java.security.spec.AlgorithmParameterSpec, java.security.Provider, java.security.SecureRandom);
|
||||||
|
+ public java.security.KeyPair getKeyPair(char[]) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException;
|
||||||
|
+ public java.security.KeyPair getKeyPair(java.security.Key, java.security.Provider) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException;
|
||||||
|
+ public java.security.PrivateKey getKey(char[]) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException;
|
||||||
|
+ public java.security.PrivateKey getKey(java.security.Key, java.security.Provider) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException;
|
||||||
|
+ public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.DEREncodable, char[]);
|
||||||
|
+ public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.DEREncodable, char[], java.lang.String, java.security.spec.AlgorithmParameterSpec, java.security.Provider);
|
||||||
|
+ public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.DEREncodable, java.security.Key, java.lang.String, java.security.spec.AlgorithmParameterSpec, java.security.Provider, java.security.SecureRandom);
|
||||||
|
javax.imageio.spi.ServiceRegistry
|
||||||
|
- public void finalize() throws java.lang.Throwable;
|
||||||
|
javax.lang.model.SourceVersion
|
||||||
|
+ public static final javax.lang.model.SourceVersion RELEASE_26;
|
||||||
|
javax.management.modelmbean.DescriptorSupport
|
||||||
|
- public javax.management.modelmbean.DescriptorSupport(java.lang.String) throws javax.management.MBeanException, javax.management.RuntimeOperationsException, javax.management.modelmbean.XMLParseException;
|
||||||
|
- public synchronized java.lang.String toXMLString();
|
||||||
|
javax.swing.RepaintManager
|
||||||
|
- public void addDirtyRegion(java.applet.Applet, int, int, int, int);
|
||||||
|
jdk.incubator.vector.VectorOperators
|
||||||
|
- public static final jdk.incubator.vector.VectorOperators$Binary SUADD;
|
||||||
|
+ public static final jdk.incubator.vector.VectorOperators$Associative SUADD;
|
||||||
|
jdk.jpackage.main.Main
|
||||||
|
- public int execute(java.io.PrintWriter, java.io.PrintWriter, java.lang.String...);
|
||||||
|
- public static final java.util.ResourceBundle I18N;
|
||||||
|
- public static void main(java.lang.String...) throws java.lang.Exception;
|
||||||
|
+ public static void main(java.lang.String...);
|
||||||
|
jdk.jshell.SourceCodeAnalysis
|
||||||
|
+ public abstract <S> java.util.List<S> completionSuggestions(java.lang.String, int, jdk.jshell.SourceCodeAnalysis$ElementSuggestionConvertor<S>);
|
||||||
|
jdk.jshell.SourceCodeAnalysis$Documentation
|
||||||
|
+ public default int activeParameterIndex();
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# JVM options that JDK 27 removed or renamed. Each runs 'java <option> -version' on 26 and on 27.
|
||||||
|
|
||||||
|
=== -noverify
|
||||||
|
JDK 26:
|
||||||
|
OpenJDK 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release.
|
||||||
|
OpenJDK 64-Bit Server VM Corretto-26.0.2.11.1 (build 26.0.2.1+11-FR, mixed mode, sharing)
|
||||||
|
JDK 27:
|
||||||
|
Unrecognized option: -noverify
|
||||||
|
Error: Could not create the Java Virtual Machine.
|
||||||
|
|
||||||
|
=== -Xverify:none
|
||||||
|
JDK 26:
|
||||||
|
OpenJDK 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release.
|
||||||
|
OpenJDK 64-Bit Server VM Corretto-26.0.2.11.1 (build 26.0.2.1+11-FR, mixed mode, sharing)
|
||||||
|
JDK 27:
|
||||||
|
Unrecognized verification option: -Xverify:none
|
||||||
|
Error: Could not create the Java Virtual Machine.
|
||||||
|
|
||||||
|
=== -noclassgc
|
||||||
|
JDK 26:
|
||||||
|
Warning: -noclassgc option is deprecated and may be removed in a future release.
|
||||||
|
OpenJDK 64-Bit Server VM Corretto-26.0.2.11.1 (build 26.0.2.1+11-FR, mixed mode, sharing)
|
||||||
|
JDK 27:
|
||||||
|
Unrecognized option: -noclassgc
|
||||||
|
Error: Could not create the Java Virtual Machine.
|
||||||
|
|
||||||
|
=== -XX:+UseCompressedClassPointers
|
||||||
|
JDK 26:
|
||||||
|
OpenJDK 64-Bit Server VM warning: Option UseCompressedClassPointers was deprecated in version 25.0 and will likely be removed in a future release.
|
||||||
|
OpenJDK 64-Bit Server VM Corretto-26.0.2.11.1 (build 26.0.2.1+11-FR, mixed mode, sharing)
|
||||||
|
JDK 27:
|
||||||
|
OpenJDK 64-Bit Server VM warning: Ignoring option UseCompressedClassPointers; support was removed in 27.0
|
||||||
|
OpenJDK 64-Bit Server VM Corretto-27.0.0.35.1 (build 27+35-FR, mixed mode, sharing)
|
||||||
|
|
||||||
|
=== -XX:InitiatingHeapOccupancyPercent=40
|
||||||
|
JDK 26:
|
||||||
|
OpenJDK 64-Bit Server VM Corretto-26.0.2.11.1 (build 26.0.2.1+11-FR, mixed mode, sharing)
|
||||||
|
JDK 27:
|
||||||
|
OpenJDK 64-Bit Server VM warning: Option InitiatingHeapOccupancyPercent was deprecated in version 27.0 and will likely be removed in a future release. Use option G1IHOP instead.
|
||||||
|
OpenJDK 64-Bit Server VM Corretto-27.0.0.35.1 (build 27+35-FR, mixed mode, sharing)
|
||||||
|
|
||||||
|
=== -XX:G1IHOP=40
|
||||||
|
JDK 26:
|
||||||
|
Unrecognized VM option 'G1IHOP=40'
|
||||||
|
Error: Could not create the Java Virtual Machine.
|
||||||
|
JDK 27:
|
||||||
|
OpenJDK 64-Bit Server VM Corretto-27.0.0.35.1 (build 27+35-FR, mixed mode, sharing)
|
||||||
|
|
||||||
|
=== -XX:+UnlockExperimentalVMOptions -XX:+UseGraalJIT (JVMCI and the Graal JIT hook were removed in 27)
|
||||||
|
JDK 26:
|
||||||
|
Cannot use JVMCI compiler: JVMCI compiler 'graal' specified by jvmci.Compiler not found
|
||||||
|
JDK 27:
|
||||||
|
Unrecognized VM option 'UseGraalJIT'
|
||||||
|
Error: Could not create the Java Virtual Machine.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Behaviour changes that need no new API to observe
|
||||||
|
|
||||||
|
=== a script that pins the VFORK process launch mechanism
|
||||||
|
JDK 26: java -Djdk.lang.Process.launchMechanism=VFORK SpawnProbe
|
||||||
|
VFORK MODE DEPRECATED
|
||||||
|
The VFORK launch mechanism has been deprecated for being dangerous.
|
||||||
|
It will be removed in a future java version. Either remove the
|
||||||
|
JDK 27: java -Djdk.lang.Process.launchMechanism=VFORK SpawnProbe
|
||||||
|
The VFORK launch mechanism has been removed. Switching to FORK instead.
|
||||||
|
Please remove the jdk.lang.Process.launchMechanism property (preferred)
|
||||||
|
or use FORK mode instead (-Djdk.lang.Process.launchMechanism=FORK).
|
||||||
|
|
||||||
|
=== the legacy ISO 639 language codes property (iw, ji, in)
|
||||||
|
JDK 26: java -Djava.locale.useOldISOCodes=true Compat
|
||||||
|
java.version = 26.0.2.1
|
||||||
|
WARNING: The use of the system property "java.locale.useOldISOCodes" is deprecated. It will be removed in a future release of the JDK.
|
||||||
|
Locale("iw").language = iw (Hebrew; old code 'iw', current code 'he')
|
||||||
|
JDK 27: java -Djava.locale.useOldISOCodes=true Compat
|
||||||
|
java.version = 27
|
||||||
|
WARNING: The system property "java.locale.useOldISOCodes" is no longer supported. Any specified value will be ignored.
|
||||||
|
Locale("iw").language = he (Hebrew; old code 'iw', current code 'he')
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# New public API in 27, each exercised once (found by diffing javap dumps, see 51-api-diff-26-to-27.txt)
|
||||||
|
Math.acosh(2) = 1.316958
|
||||||
|
Math.asinh(1) = 0.881374
|
||||||
|
Math.atanh(.5) = 0.549306
|
||||||
|
CHECK ok : acosh inverts cosh
|
||||||
|
"naïve café".length() = 10, encodedLength(UTF_8) = 12
|
||||||
|
CHECK ok : encodedLength matches getBytes().length without allocating the array
|
||||||
|
BigDecimal(27).rootn(3) = 3
|
||||||
|
CHECK ok : rootn(3) of 27 is 3
|
||||||
|
KeyStore.getCreationInstant("aes-key") returns an Instant: true
|
||||||
|
CHECK ok : KeyStore.getCreationInstant returns the entry's creation time as a java.time.Instant
|
||||||
|
CHECK ok : Set.ofLazy(Set, Predicate) exists (preview API)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# JEP 500: reflection writing to a final field, same class file behaviour on 25 and on 26
|
||||||
|
|
||||||
|
=== JDK 25 (class compiled --release 25)
|
||||||
|
field reads : 9090
|
||||||
|
port() returns : 8080
|
||||||
|
|
||||||
|
=== JDK 26, defaults
|
||||||
|
WARNING: Final field port in class FinalFieldMutation$Config has been mutated reflectively by class FinalFieldMutation in unnamed module @<hash> (file:<repo>/build/recap26/26/)
|
||||||
|
WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning
|
||||||
|
WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled
|
||||||
|
field reads : 9090
|
||||||
|
port() returns : 8080
|
||||||
|
|
||||||
|
=== JDK 26, --enable-final-field-mutation=ALL-UNNAMED (the opt-in the warning asks for)
|
||||||
|
field reads : 9090
|
||||||
|
port() returns : 8080
|
||||||
|
|
||||||
|
=== JDK 26, --illegal-final-field-mutation=deny (what a future default will look like)
|
||||||
|
setInt refused : class FinalFieldMutation (in unnamed module @<hash>) cannot set final field FinalFieldMutation$Config.port (in unnamed module @<hash>), unnamed module @<hash> is not allowed to mutate final fields
|
||||||
|
|
||||||
|
=== JDK 26, --illegal-final-field-mutation=debug (warning plus the offending stack trace)
|
||||||
|
Final field port in class FinalFieldMutation$Config has been mutated reflectively by class FinalFieldMutation in unnamed module @<hash> (file:<repo>/build/recap26/26/)
|
||||||
|
at java.base/java.lang.reflect.Field.postSetFinal(Field.java:1534)
|
||||||
|
at java.base/java.lang.reflect.Field.setFinal(Field.java:1449)
|
||||||
|
at java.base/java.lang.reflect.Field.setInt(Field.java:1134)
|
||||||
|
at FinalFieldMutation.main(FinalFieldMutation.java:22)
|
||||||
|
field reads : 9090
|
||||||
|
port() returns : 8080
|
||||||
|
|
||||||
|
=== JDK 26, JFR default settings, [no flag]: jfr print --events jdk.FinalFieldMutation
|
||||||
|
jdk.FinalFieldMutation
|
||||||
|
declaringClass = FinalFieldMutation$Config (classLoader = app)
|
||||||
|
fieldName = "port"
|
||||||
|
FinalFieldMutation.main(String[]) line: 22
|
||||||
|
events recorded: 1
|
||||||
|
|
||||||
|
=== JDK 26, JFR default settings, [--enable-final-field-mutation=ALL-UNNAMED]: jfr print --events jdk.FinalFieldMutation
|
||||||
|
jdk.FinalFieldMutation
|
||||||
|
declaringClass = FinalFieldMutation$Config (classLoader = app)
|
||||||
|
fieldName = "port"
|
||||||
|
FinalFieldMutation.main(String[]) line: 22
|
||||||
|
events recorded: 1
|
||||||
|
|
||||||
|
=== JDK 26, JFR default settings, [--illegal-final-field-mutation=deny]: jfr print --events jdk.FinalFieldMutation
|
||||||
|
events recorded: 0
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# JEP 516: can the cache's archived heap objects be used? One row per (JDK, GC the cache was built under, GC at run time).
|
||||||
|
# 'heap objects' = the log says the archived module graph was used; 'unusable' = the JVM refused the whole cache.
|
||||||
|
JDK 25 cache built under G1 run under Serial -> heap objects used (full module graph: enabled)
|
||||||
|
JDK 25 cache built under G1 run under G1 -> heap objects used (full module graph: enabled)
|
||||||
|
JDK 25 cache built under G1 run under Z -> unusable: saved state of UseCompressedOops and UseCompressedClassPointers is different from runtime
|
||||||
|
JDK 25 cache built under Z run under Serial -> unusable: saved state of UseCompressedOops and UseCompressedClassPointers is different from runtime
|
||||||
|
JDK 25 cache built under Z run under G1 -> unusable: saved state of UseCompressedOops and UseCompressedClassPointers is different from runtime
|
||||||
|
JDK 25 cache built under Z run under Z -> cache used, heap objects NOT used (full module graph: disabled)
|
||||||
|
JDK 26 cache built under G1 run under Serial -> heap objects used (full module graph: enabled)
|
||||||
|
JDK 26 cache built under G1 run under G1 -> heap objects used (full module graph: enabled)
|
||||||
|
JDK 26 cache built under G1 run under Z -> unusable: saved state of UseCompressedOops and UseCompressedClassPointers is different from runtime
|
||||||
|
JDK 26 cache built under Z run under Serial -> unusable: saved state of UseCompressedOops and UseCompressedClassPointers is different from runtime
|
||||||
|
JDK 26 cache built under Z run under G1 -> unusable: saved state of UseCompressedOops and UseCompressedClassPointers is different from runtime
|
||||||
|
JDK 26 cache built under Z run under Z -> heap objects used (full module graph: enabled)
|
||||||
|
JDK 27 cache built under G1 run under Serial -> heap objects used (full module graph: enabled)
|
||||||
|
JDK 27 cache built under G1 run under G1 -> heap objects used (full module graph: enabled)
|
||||||
|
JDK 27 cache built under G1 run under Z -> unusable: saved state of UseCompressedOops (1) is different from runtime (0)
|
||||||
|
JDK 27 cache built under Z run under Serial -> unusable: saved state of UseCompressedOops (0) is different from runtime (1)
|
||||||
|
JDK 27 cache built under Z run under G1 -> unusable: saved state of UseCompressedOops (0) is different from runtime (1)
|
||||||
|
JDK 27 cache built under Z run under Z -> heap objects used (full module graph: enabled)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Wall-clock ms for 'java -cp app.jar AotApp' (median of 9), no AOT cache vs cache built under the same GC. INDICATIVE: shared 2-CPU sandbox.
|
||||||
|
JDK 25 G1 no cache: 57 ms with cache: 37 ms
|
||||||
|
JDK 25 Z no cache: 83 ms with cache: 81 ms
|
||||||
|
JDK 26 G1 no cache: 59 ms with cache: 35 ms
|
||||||
|
JDK 26 Z no cache: 62 ms with cache: 40 ms
|
||||||
|
JDK 27 G1 no cache: 61 ms with cache: 36 ms
|
||||||
|
JDK 27 Z no cache: 63 ms with cache: 38 ms
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Source that compiled on 25 and does not on 26
|
||||||
|
|
||||||
|
=== recap26/src/broken/AppletGone.java
|
||||||
|
javac (JDK 25, --release 25): exit 0
|
||||||
|
<repo>/recap26/src/broken/AppletGone.java:5: warning: [removal] Applet in java.applet has been deprecated and marked for removal
|
||||||
|
public class AppletGone extends Applet {
|
||||||
|
^
|
||||||
|
1 warning
|
||||||
|
javac (JDK 26, --release 26): exit 1
|
||||||
|
<repo>/recap26/src/broken/AppletGone.java:3: error: package java.applet does not exist
|
||||||
|
import java.applet.Applet;
|
||||||
|
^
|
||||||
|
<repo>/recap26/src/broken/AppletGone.java:5: error: cannot find symbol
|
||||||
|
public class AppletGone extends Applet {
|
||||||
|
^
|
||||||
|
symbol: class Applet
|
||||||
|
<repo>/recap26/src/broken/AppletGone.java:6: error: init() in AppletGone does not override or implement a method from a supertype
|
||||||
|
@Override public void init() { System.out.println("applet init"); }
|
||||||
|
^
|
||||||
|
3 errors
|
||||||
|
|
||||||
|
=== recap26/src/broken/ThreadStopGone.java
|
||||||
|
javac (JDK 25, --release 25): exit 0
|
||||||
|
<repo>/recap26/src/broken/ThreadStopGone.java:5: warning: [removal] stop() in Thread has been deprecated and marked for removal
|
||||||
|
t.stop();
|
||||||
|
^
|
||||||
|
1 warning
|
||||||
|
javac (JDK 26, --release 26): exit 1
|
||||||
|
<repo>/recap26/src/broken/ThreadStopGone.java:5: error: cannot find symbol
|
||||||
|
t.stop();
|
||||||
|
^
|
||||||
|
symbol: method stop()
|
||||||
|
location: variable t of type Thread
|
||||||
|
1 error
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
CHECK ok : "STRASSE".equalsFoldCase("stra" + sharp s + "e") is true (full case folding)
|
||||||
|
CHECK ok : equalsIgnoreCase says false for the same pair (simple folding only)
|
||||||
|
CHECK ok : "apple".compareToFoldCase("BANANA") < 0
|
||||||
|
CHECK ok : sorted with String.UNICODE_CASEFOLD_ORDER: [A, b, B, c]
|
||||||
|
CHECK ok : UUID.ofEpochMillis(..).version() == 7
|
||||||
|
CHECK ok : the first 48 bits are the timestamp: 018bcfe5-6800-...
|
||||||
|
CHECK ok : try-with-resources on Process (implements Closeable in 26)
|
||||||
|
CHECK ok : MemoryMXBean.getTotalGcCpuTime() exists and is non-negative
|
||||||
|
CHECK ok : ByteOrder is an enum: [LITTLE_ENDIAN, BIG_ENDIAN]
|
||||||
|
CHECK ok : HttpClient.Version.HTTP_3 exists and is accepted by the builder
|
||||||
|
CHECK ok : Character.UnicodeBlock.SIDETIC exists (Unicode 17)
|
||||||
|
exit code: 0
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# JEP 517: a client that prefers HTTP/3 against a server that only speaks HTTP/1.1 over TLS (loopback, JDK's own HttpsServer)
|
||||||
|
# There is no live HTTP/3 server in this transcript: the sandbox has no UDP egress. The API surface is javap output below.
|
||||||
|
client asked for : HTTP_3
|
||||||
|
server answered : HTTP_1_1 200 hello
|
||||||
|
HTTP_3_URI_ONLY : java.net.http.HttpConnectTimeoutException: quic handshake timeout
|
||||||
|
|
||||||
|
# javap --module java.net.http -public java.net.http.HttpOption 'java.net.http.HttpOption$Http3DiscoveryMode'
|
||||||
|
Compiled from "HttpOption.java"
|
||||||
|
public interface java.net.http.HttpOption<T> {
|
||||||
|
public static final java.net.http.HttpOption<java.net.http.HttpOption$Http3DiscoveryMode> H3_DISCOVERY;
|
||||||
|
public abstract java.lang.String name();
|
||||||
|
public abstract java.lang.Class<T> type();
|
||||||
|
}
|
||||||
|
Compiled from "HttpOption.java"
|
||||||
|
public final class java.net.http.HttpOption$Http3DiscoveryMode extends java.lang.Enum<java.net.http.HttpOption$Http3DiscoveryMode> {
|
||||||
|
public static final java.net.http.HttpOption$Http3DiscoveryMode ANY;
|
||||||
|
public static final java.net.http.HttpOption$Http3DiscoveryMode ALT_SVC;
|
||||||
|
public static final java.net.http.HttpOption$Http3DiscoveryMode HTTP_3_URI_ONLY;
|
||||||
|
public static java.net.http.HttpOption$Http3DiscoveryMode[] values();
|
||||||
|
public static java.net.http.HttpOption$Http3DiscoveryMode valueOf(java.lang.String);
|
||||||
|
}
|
||||||
|
|
||||||
|
# javap ... HttpClient$Version
|
||||||
|
public static final java.net.http.HttpClient$Version HTTP_1_1;
|
||||||
|
public static final java.net.http.HttpClient$Version HTTP_2;
|
||||||
|
public static final java.net.http.HttpClient$Version HTTP_3;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import com.sun.management.HotSpotDiagnosticMXBean;
|
||||||
|
import java.lang.management.GarbageCollectorMXBean;
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints which garbage collector the JVM picked when nobody asked for one, and why.
|
||||||
|
*
|
||||||
|
* <p>The "why" is the interesting part: HotSpot records the ORIGIN of every flag value.
|
||||||
|
* {@code ERGONOMIC} means the JVM chose it by looking at the machine; {@code DEFAULT} means
|
||||||
|
* it is simply the built-in value; {@code COMMAND_LINE} means somebody typed it.
|
||||||
|
*
|
||||||
|
* <p>Explained in docs/02-g1-default.md. Run by scripts/g1-default.sh.
|
||||||
|
*/
|
||||||
|
public class GcReport {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Runtime rt = Runtime.getRuntime();
|
||||||
|
System.out.println("java.version = " + System.getProperty("java.version"));
|
||||||
|
System.out.println("availableCpus = " + rt.availableProcessors());
|
||||||
|
System.out.println("maxHeapMB = " + rt.maxMemory() / (1024 * 1024));
|
||||||
|
System.out.println("collector beans = " + ManagementFactory.getGarbageCollectorMXBeans().stream()
|
||||||
|
.map(GarbageCollectorMXBean::getName).collect(Collectors.joining(", ")));
|
||||||
|
|
||||||
|
HotSpotDiagnosticMXBean hs = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class);
|
||||||
|
for (String flag : new String[] {"UseG1GC", "UseSerialGC", "UseParallelGC"}) {
|
||||||
|
var opt = hs.getVMOption(flag);
|
||||||
|
System.out.printf("%-17s = %-5s (origin %s)%n", flag, opt.getValue(), opt.getOrigin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import java.lang.management.GarbageCollectorMXBean;
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A deliberately boring allocation workload, used to ask one question: what does the switch from
|
||||||
|
* Serial to G1 cost (or save) on a machine that used to get Serial?
|
||||||
|
*
|
||||||
|
* <p>It allocates small objects as fast as it can, keeps roughly 1% of them alive in a ring buffer
|
||||||
|
* (so the collector has real work to do, not just an empty young generation), and reports wall
|
||||||
|
* time, collection count and time, the slowest single batch (a rough stand-in for the worst
|
||||||
|
* stall an application thread saw), and the process's peak resident memory from /proc.
|
||||||
|
*
|
||||||
|
* <p>Timing numbers are indicative, not a benchmark: one process, one run, a shared machine.
|
||||||
|
* The point is the SHAPE of the difference. See docs/02-g1-default.md.
|
||||||
|
*/
|
||||||
|
public class Workload {
|
||||||
|
|
||||||
|
record Item(long id, String label, byte[] payload) {}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
int batches = args.length > 0 ? Integer.parseInt(args[0]) : 400;
|
||||||
|
int perBatch = 50_000;
|
||||||
|
Item[] ring = new Item[100_000];
|
||||||
|
int ringPos = 0;
|
||||||
|
long checksum = 0;
|
||||||
|
long slowestBatchNanos = 0;
|
||||||
|
|
||||||
|
long start = System.nanoTime();
|
||||||
|
for (int b = 0; b < batches; b++) {
|
||||||
|
long t0 = System.nanoTime();
|
||||||
|
for (int i = 0; i < perBatch; i++) {
|
||||||
|
long id = (long) b * perBatch + i;
|
||||||
|
Item item = new Item(id, "item-" + id, new byte[64 + (int) (id & 63)]);
|
||||||
|
checksum += item.payload().length;
|
||||||
|
if (id % 100 == 0) {
|
||||||
|
ring[ringPos] = item;
|
||||||
|
ringPos = (ringPos + 1) % ring.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slowestBatchNanos = Math.max(slowestBatchNanos, System.nanoTime() - t0);
|
||||||
|
}
|
||||||
|
long elapsedMs = (System.nanoTime() - start) / 1_000_000;
|
||||||
|
|
||||||
|
long gcCount = 0, gcMs = 0;
|
||||||
|
for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
|
||||||
|
gcCount += Math.max(0, gc.getCollectionCount());
|
||||||
|
gcMs += Math.max(0, gc.getCollectionTime());
|
||||||
|
}
|
||||||
|
System.out.printf("allocated objects = %,d (checksum %d)%n", (long) batches * perBatch, checksum);
|
||||||
|
System.out.printf("wall time = %d ms%n", elapsedMs);
|
||||||
|
System.out.printf("gc count / time = %d / %d ms%n", gcCount, gcMs);
|
||||||
|
System.out.printf("slowest batch = %d ms%n", slowestBatchNanos / 1_000_000);
|
||||||
|
System.out.printf("peak RSS = %d MB%n", peakRssKb() / 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** VmHWM is the high-water mark of the resident set, in kB. */
|
||||||
|
static long peakRssKb() throws Exception {
|
||||||
|
for (String line : Files.readAllLines(Path.of("/proc/self/status"))) {
|
||||||
|
if (line.startsWith("VmHWM:")) {
|
||||||
|
return Long.parseLong(line.replaceAll("\\D+", ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Tiny assertion helper so every demo is self-checking: a claim that stops being true turns the
|
||||||
|
* run red instead of quietly printing something different. Passing checks are echoed, so the
|
||||||
|
* transcript shows exactly what was verified.
|
||||||
|
*/
|
||||||
|
final class Check {
|
||||||
|
private Check() {}
|
||||||
|
|
||||||
|
static void that(boolean condition, String claim) {
|
||||||
|
if (!condition) {
|
||||||
|
throw new AssertionError("CHECK FAILED: " + claim);
|
||||||
|
}
|
||||||
|
System.out.println("CHECK ok : " + claim);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How big is the TLS ClientHello this JDK sends?
|
||||||
|
*
|
||||||
|
* <p>A plain TCP server accepts the connection, reads the first TLS record header (5 bytes: type,
|
||||||
|
* version, 2-byte length) and then the record body, and reports the length. The client handshake
|
||||||
|
* never completes, and does not need to. Post-quantum key shares are large: this is the number that
|
||||||
|
* decides whether a hello still fits in one network packet. Explained in docs/04-post-quantum-tls.md.
|
||||||
|
*/
|
||||||
|
public class ClientHelloSize {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
try (ServerSocket listener = new ServerSocket(0, 1, InetAddress.getLoopbackAddress())) {
|
||||||
|
Thread client = new Thread(() -> {
|
||||||
|
try (SSLSocket s = (SSLSocket) SSLSocketFactory.getDefault()
|
||||||
|
.createSocket(InetAddress.getLoopbackAddress(), listener.getLocalPort())) {
|
||||||
|
s.setSoTimeout(1_000);
|
||||||
|
s.startHandshake(); // will time out: nobody answers
|
||||||
|
} catch (Exception expected) {
|
||||||
|
// the server side closes without replying
|
||||||
|
}
|
||||||
|
});
|
||||||
|
client.start();
|
||||||
|
try (Socket raw = listener.accept()) {
|
||||||
|
DataInputStream in = new DataInputStream(raw.getInputStream());
|
||||||
|
int type = in.readUnsignedByte();
|
||||||
|
int version = in.readUnsignedShort();
|
||||||
|
int length = in.readUnsignedShort();
|
||||||
|
byte[] body = new byte[length];
|
||||||
|
in.readFully(body);
|
||||||
|
System.out.printf("java.version = %s%n", System.getProperty("java.version"));
|
||||||
|
System.out.printf("record type = 0x%02x (0x16 = handshake)%n", type);
|
||||||
|
System.out.printf("ClientHello record = %d bytes (+5 byte record header)%n", length);
|
||||||
|
System.out.printf("fits one TCP segment = %s (1460-byte MSS on a 1500-byte MTU)%n", length + 5 <= 1460 ? "yes" : "NO");
|
||||||
|
}
|
||||||
|
client.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/** Does nothing for a second. Exists only so a JVM has arguments, properties and environment for JFR to record. */
|
||||||
|
public class Idle {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
Thread.sleep(1_000);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JEP 531, Lazy Constants (third preview), against the JDK 27 API.
|
||||||
|
*
|
||||||
|
* <p>A lazy constant is a value that is computed the first time somebody asks for it and never again,
|
||||||
|
* even when many threads ask at once. The JVM can then treat it like a {@code final} field. What changed
|
||||||
|
* since JDK 26: {@code orElse(...)} and {@code isInitialized()} are gone (see broken/Lazy26.java), and
|
||||||
|
* {@code Set.ofLazy(...)} arrived next to {@code List.ofLazy} and {@code Map.ofLazy}.
|
||||||
|
* Explained in docs/06-lazy-constants.md. Compile and run with --enable-preview.
|
||||||
|
*/
|
||||||
|
public class LazyDemo {
|
||||||
|
|
||||||
|
static final AtomicInteger INITIALISATIONS = new AtomicInteger();
|
||||||
|
|
||||||
|
/** The expensive thing: pretend this reads a file and parses it. */
|
||||||
|
record Settings(String region, int poolSize) {}
|
||||||
|
|
||||||
|
static Settings loadSettings() {
|
||||||
|
INITIALISATIONS.incrementAndGet();
|
||||||
|
System.out.println(" (loading settings on " + Thread.currentThread().getName() + ")");
|
||||||
|
return new Settings("ap-south-1", 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final LazyConstant<Settings> SETTINGS = LazyConstant.of(LazyDemo::loadSettings);
|
||||||
|
|
||||||
|
/** Identity hashes and lambda addresses change on every run; blank them so the transcript is stable. */
|
||||||
|
static String tidy(Object o) {
|
||||||
|
return o.toString().replaceAll("@[0-9a-f]+", "@...").replaceAll("/0x[0-9a-f]+", "/0x...");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
System.out.println("--- a lazy constant computes once, on first use");
|
||||||
|
System.out.println("toString before first get(): " + tidy(SETTINGS));
|
||||||
|
Settings first = SETTINGS.get();
|
||||||
|
Settings second = SETTINGS.get();
|
||||||
|
System.out.println("toString after first get(): " + tidy(SETTINGS));
|
||||||
|
Check.that(first == second, "get() returns the same instance every time");
|
||||||
|
Check.that(INITIALISATIONS.get() == 1, "the supplier ran exactly once");
|
||||||
|
|
||||||
|
System.out.println("--- and once even when 64 threads race for it");
|
||||||
|
var raced = LazyConstant.of(() -> {
|
||||||
|
INITIALISATIONS.incrementAndGet();
|
||||||
|
return "computed";
|
||||||
|
});
|
||||||
|
int before = INITIALISATIONS.get();
|
||||||
|
var start = new CountDownLatch(1);
|
||||||
|
try (var pool = Executors.newVirtualThreadPerTaskExecutor()) {
|
||||||
|
for (int i = 0; i < 64; i++) {
|
||||||
|
pool.submit(() -> {
|
||||||
|
start.await();
|
||||||
|
return raced.get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
start.countDown();
|
||||||
|
}
|
||||||
|
Check.that(INITIALISATIONS.get() - before == 1, "64 racing threads triggered exactly one initialisation");
|
||||||
|
|
||||||
|
System.out.println("--- List.ofLazy: each element is computed on first access");
|
||||||
|
AtomicInteger listComputations = new AtomicInteger();
|
||||||
|
List<String> squares = List.ofLazy(5, i -> {
|
||||||
|
listComputations.incrementAndGet();
|
||||||
|
return "square(" + i + ")=" + (i * i);
|
||||||
|
});
|
||||||
|
System.out.println("computed so far: " + listComputations.get());
|
||||||
|
System.out.println("squares.get(3) = " + squares.get(3));
|
||||||
|
System.out.println("squares.get(3) = " + squares.get(3) + " (second read)");
|
||||||
|
Check.that(listComputations.get() == 1, "only element 3 was ever computed");
|
||||||
|
|
||||||
|
System.out.println("--- Map.ofLazy: the keys are fixed, the values are computed on first access");
|
||||||
|
AtomicInteger mapComputations = new AtomicInteger();
|
||||||
|
Map<String, Integer> lengths = Map.ofLazy(Set.of("alpha", "beta", "gamma"), k -> {
|
||||||
|
mapComputations.incrementAndGet();
|
||||||
|
return k.length();
|
||||||
|
});
|
||||||
|
System.out.println("lengths.get(\"gamma\") = " + lengths.get("gamma"));
|
||||||
|
Check.that(mapComputations.get() == 1, "only the requested key's value was computed");
|
||||||
|
|
||||||
|
System.out.println("--- Set.ofLazy (new in 27): membership is decided by a predicate, on demand");
|
||||||
|
AtomicInteger setComputations = new AtomicInteger();
|
||||||
|
Set<Integer> primes = Set.ofLazy(Set.of(2, 3, 4, 5, 6, 7), n -> {
|
||||||
|
setComputations.incrementAndGet();
|
||||||
|
return n > 1 && java.util.stream.IntStream.rangeClosed(2, (int) Math.sqrt(n)).noneMatch(d -> n % d == 0);
|
||||||
|
});
|
||||||
|
System.out.println("primes.contains(7) = " + primes.contains(7) + ", primes.contains(6) = " + primes.contains(6));
|
||||||
|
Check.that(primes.contains(7) && !primes.contains(6), "the lazy set answers membership using the predicate");
|
||||||
|
System.out.println("primes = " + new java.util.TreeSet<>(primes));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.BinaryEncodable;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.PEM;
|
||||||
|
import java.security.PEMDecoder;
|
||||||
|
import java.security.PEMEncoder;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JEP 538, PEM Encodings of Cryptographic Objects (third preview), against the JDK 27 API.
|
||||||
|
*
|
||||||
|
* <p>PEM is the "-----BEGIN ...-----" text format every key and certificate file on a Linux box is in.
|
||||||
|
* Before this API, turning a key into PEM or back meant hand-rolled Base64 and header strings.
|
||||||
|
* Note the interface name: JDK 26 called the common supertype {@code DEREncodable}; JDK 27 renamed it
|
||||||
|
* {@code BinaryEncodable}. See broken/Pem26Style.java. Explained in docs/08-pem-api.md.
|
||||||
|
* Compile and run with --enable-preview.
|
||||||
|
*/
|
||||||
|
public class PemDemo {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
KeyPairGenerator gen = KeyPairGenerator.getInstance("EC");
|
||||||
|
gen.initialize(256);
|
||||||
|
KeyPair pair = gen.generateKeyPair();
|
||||||
|
|
||||||
|
// 1. Encode: one call, no Base64 in sight.
|
||||||
|
String publicPem = PEMEncoder.of().encodeToString(pair.getPublic());
|
||||||
|
String privatePem = PEMEncoder.of().encodeToString(pair.getPrivate());
|
||||||
|
System.out.println("--- public key, encoded");
|
||||||
|
System.out.println(firstLines(publicPem, 1) + " ... " + lastLine(publicPem));
|
||||||
|
System.out.println("--- private key, encoded");
|
||||||
|
System.out.println(firstLines(privatePem, 1) + " ... " + lastLine(privatePem));
|
||||||
|
Check.that(publicPem.startsWith("-----BEGIN PUBLIC KEY-----"), "a PublicKey encodes as BEGIN PUBLIC KEY");
|
||||||
|
Check.that(privatePem.startsWith("-----BEGIN PRIVATE KEY-----"), "a PrivateKey encodes as BEGIN PRIVATE KEY (PKCS#8)");
|
||||||
|
|
||||||
|
// 2. Decode, asking for the type you expect.
|
||||||
|
PublicKey publicBack = PEMDecoder.of().decode(publicPem, PublicKey.class);
|
||||||
|
PrivateKey privateBack = PEMDecoder.of().decode(privatePem, PrivateKey.class);
|
||||||
|
Check.that(publicBack.equals(pair.getPublic()), "the decoded public key equals the original");
|
||||||
|
Check.that(privateBack.equals(pair.getPrivate()), "the decoded private key equals the original");
|
||||||
|
|
||||||
|
// 3. Decode without saying what you expect, and switch on what came back.
|
||||||
|
System.out.println("--- decode(String) returns whatever the header says it is");
|
||||||
|
for (String pem : new String[] {publicPem, privatePem}) {
|
||||||
|
BinaryEncodable decoded = PEMDecoder.of().decode(pem);
|
||||||
|
String kind = switch (decoded) {
|
||||||
|
case PublicKey k -> "PublicKey (" + k.getAlgorithm() + ")";
|
||||||
|
case PrivateKey k -> "PrivateKey (" + k.getAlgorithm() + ")";
|
||||||
|
default -> decoded.getClass().getName();
|
||||||
|
};
|
||||||
|
System.out.println(pem.lines().findFirst().orElse("") + " -> " + kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Encrypted private keys: the password goes on the encoder and the decoder.
|
||||||
|
char[] password = "correct horse".toCharArray();
|
||||||
|
String encryptedPem = PEMEncoder.of().withEncryption(password).encodeToString(pair.getPrivate());
|
||||||
|
System.out.println("--- encrypted private key");
|
||||||
|
System.out.println(firstLines(encryptedPem, 1));
|
||||||
|
Check.that(encryptedPem.startsWith("-----BEGIN ENCRYPTED PRIVATE KEY-----"), "withEncryption produces ENCRYPTED PRIVATE KEY");
|
||||||
|
|
||||||
|
BinaryEncodable withoutPassword = PEMDecoder.of().decode(encryptedPem);
|
||||||
|
System.out.println("decoded with no password: " + withoutPassword.getClass().getName());
|
||||||
|
Check.that(withoutPassword instanceof EncryptedPrivateKeyInfo, "without a password you get the still-encrypted structure back");
|
||||||
|
|
||||||
|
PrivateKey decrypted = PEMDecoder.of().withDecryption(password).decode(encryptedPem, PrivateKey.class);
|
||||||
|
Check.that(decrypted.equals(pair.getPrivate()), "with the password you get the original private key");
|
||||||
|
|
||||||
|
// 5. A PEM type the JDK does not know: it round-trips as a raw PEM object.
|
||||||
|
// The trap: PEM's content is the Base64 TEXT, not the payload. Hand it raw bytes and they are written verbatim.
|
||||||
|
byte[] payload = "hello, pem".getBytes(StandardCharsets.UTF_8);
|
||||||
|
String wrong = PEMEncoder.of().encodeToString(new PEM("ANKURM DEMO", payload));
|
||||||
|
System.out.println("--- PEM(type, byte[]) does NOT Base64-encode: the bytes go in as they are");
|
||||||
|
System.out.println(wrong.strip());
|
||||||
|
Check.that(wrong.contains("hello, pem"), "raw bytes appear verbatim between the header and footer");
|
||||||
|
|
||||||
|
String base64 = Base64.getEncoder().encodeToString(payload);
|
||||||
|
String right = PEMEncoder.of().encodeToString(new PEM("ANKURM DEMO", base64));
|
||||||
|
System.out.println("--- give it the Base64 text and it round-trips");
|
||||||
|
System.out.println(right.strip());
|
||||||
|
BinaryEncodable raw = PEMDecoder.of().decode(right);
|
||||||
|
Check.that(raw instanceof PEM p && p.type().equals("ANKURM DEMO")
|
||||||
|
&& new String(p.content(), StandardCharsets.US_ASCII).strip().equals(base64)
|
||||||
|
&& Arrays.equals(p.decode(), payload),
|
||||||
|
"type, Base64 text (content()) and decoded payload (decode()) all survive");
|
||||||
|
}
|
||||||
|
|
||||||
|
static String firstLines(String s, int n) {
|
||||||
|
return s.lines().limit(n).reduce((a, b) -> a + "\n" + b).orElse("");
|
||||||
|
}
|
||||||
|
|
||||||
|
static String lastLine(String s) {
|
||||||
|
return s.lines().reduce((a, b) -> b).orElse("");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* JEP 532, Primitive Types in Patterns, instanceof, and switch (fifth preview).
|
||||||
|
*
|
||||||
|
* <p>Until now patterns worked on reference types only. With this preview a pattern can name a
|
||||||
|
* primitive type, and the test is "can this value be converted to that type WITHOUT losing
|
||||||
|
* information?" Explained in docs/05-primitive-patterns.md. Compile and run with --enable-preview.
|
||||||
|
*/
|
||||||
|
public class PrimitivePatterns {
|
||||||
|
|
||||||
|
record Reading(int celsius) {}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
exactness();
|
||||||
|
switchOnLong();
|
||||||
|
switchOnObject();
|
||||||
|
recordPattern();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** instanceof with a primitive type asks: is this an EXACT conversion? */
|
||||||
|
static void exactness() {
|
||||||
|
System.out.println("--- instanceof <primitive>: an exact-conversion test, not a cast");
|
||||||
|
int small = 100, big = 300;
|
||||||
|
boolean a = small instanceof byte;
|
||||||
|
boolean b = big instanceof byte;
|
||||||
|
System.out.println("100 instanceof byte = " + a);
|
||||||
|
System.out.println("300 instanceof byte = " + b + " (a byte holds -128..127)");
|
||||||
|
Check.that(a && !b, "100 converts to byte exactly, 300 does not");
|
||||||
|
|
||||||
|
if (small instanceof byte narrow) {
|
||||||
|
System.out.println("binding: byte narrow = " + narrow);
|
||||||
|
}
|
||||||
|
|
||||||
|
int precise = 16_777_217; // 2^24 + 1: the first int a float cannot hold
|
||||||
|
boolean f1 = 16_777_216 instanceof float;
|
||||||
|
boolean f2 = precise instanceof float;
|
||||||
|
System.out.println("16_777_216 instanceof float = " + f1);
|
||||||
|
System.out.println("16_777_217 instanceof float = " + f2 + " (float has a 24-bit significand)");
|
||||||
|
Check.that(f1 && !f2, "int to float is exact up to 2^24 and lossy after");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** switch over a long, with a primitive pattern and a guard. */
|
||||||
|
static String classify(long value) {
|
||||||
|
return switch (value) {
|
||||||
|
case 0L -> "zero";
|
||||||
|
case 1L -> "one";
|
||||||
|
case long v when v < 0 -> "negative (" + v + ")";
|
||||||
|
case long v -> "positive (" + v + ")";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void switchOnLong() {
|
||||||
|
System.out.println("--- switch on a long, with pattern labels and a guard");
|
||||||
|
for (long v : new long[] {0, 1, -7, 9_000_000_000L}) {
|
||||||
|
System.out.println("classify(" + v + ") = " + classify(v));
|
||||||
|
}
|
||||||
|
Check.that(classify(0).equals("zero") && classify(-7).startsWith("negative"), "long switch dispatches on constants, then patterns");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** switch over an Object: primitive patterns match the boxed types they correspond to. */
|
||||||
|
static String describe(Object o) {
|
||||||
|
return switch (o) {
|
||||||
|
case int i when i > 1000 -> "a large int " + i;
|
||||||
|
case int i -> "an int " + i;
|
||||||
|
case long l -> "a long " + l;
|
||||||
|
case double d -> "a double " + d;
|
||||||
|
case String s -> "a String of length " + s.length();
|
||||||
|
default -> "something else: " + o;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void switchOnObject() {
|
||||||
|
System.out.println("--- switch on an Object: an Integer matches 'case int'");
|
||||||
|
Object[] samples = {7, 5000, 7L, 2.5, "hello", 'x'};
|
||||||
|
for (Object o : samples) {
|
||||||
|
System.out.println("describe(" + o.getClass().getSimpleName() + " " + o + ") = " + describe(o));
|
||||||
|
}
|
||||||
|
Check.that(describe(5000).equals("a large int 5000"), "an Integer 5000 matches case int with a guard");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Record patterns can now narrow a component: Reading.celsius is an int, the pattern asks for a byte. */
|
||||||
|
static void recordPattern() {
|
||||||
|
System.out.println("--- record pattern with a narrowing primitive component");
|
||||||
|
Object[] readings = {new Reading(36), new Reading(4_000)};
|
||||||
|
for (Object o : readings) {
|
||||||
|
String verdict = switch (o) {
|
||||||
|
case Reading(byte c) -> "fits in a byte: " + c;
|
||||||
|
case Reading(int c) -> "needs a full int: " + c;
|
||||||
|
default -> "not a reading";
|
||||||
|
};
|
||||||
|
System.out.println(o + " -> " + verdict);
|
||||||
|
}
|
||||||
|
Check.that(readings[0] instanceof Reading(byte _) && !(readings[1] instanceof Reading(byte _)),
|
||||||
|
"Reading(byte c) matches 36 but not 4000");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.StructuredTaskScope;
|
||||||
|
import java.util.concurrent.StructuredTaskScope.Joiner;
|
||||||
|
import java.util.concurrent.StructuredTaskScope.Subtask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JEP 533, Structured Concurrency (seventh preview), written against the JDK 27 API.
|
||||||
|
*
|
||||||
|
* <p>The headline change from JDK 26 is that {@code StructuredTaskScope} and {@code Joiner} gained a
|
||||||
|
* third type parameter, {@code R_X}: the exception {@code join()} throws. That lets a scope throw YOUR
|
||||||
|
* exception type rather than a generic {@code FailedException}. Compare broken/StructuredScope26.java,
|
||||||
|
* which is JDK 26 code that no longer compiles. Explained in docs/07-structured-concurrency.md.
|
||||||
|
*
|
||||||
|
* <p>Compile and run with --enable-preview.
|
||||||
|
*/
|
||||||
|
public class StructuredDemo {
|
||||||
|
|
||||||
|
/** The application's own failure type: what R_X lets us surface. */
|
||||||
|
static class OrderFailed extends Exception {
|
||||||
|
OrderFailed(Throwable cause) {
|
||||||
|
super("order failed: " + cause.getMessage(), cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String fetchPrice() throws InterruptedException {
|
||||||
|
Thread.sleep(50);
|
||||||
|
return "price=42";
|
||||||
|
}
|
||||||
|
|
||||||
|
static String fetchStock() throws InterruptedException {
|
||||||
|
Thread.sleep(80);
|
||||||
|
return "stock=7";
|
||||||
|
}
|
||||||
|
|
||||||
|
static String failingCall() {
|
||||||
|
throw new IllegalStateException("inventory service is down");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
allSuccessful();
|
||||||
|
yourOwnExceptionType();
|
||||||
|
timeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The default joiner: all subtasks must succeed, failure surfaces as ExecutionException. */
|
||||||
|
static void allSuccessful() throws Exception {
|
||||||
|
System.out.println("--- allSuccessfulOrThrow(): results as a List, failures as ExecutionException");
|
||||||
|
try (var scope = StructuredTaskScope.open(Joiner.<String>allSuccessfulOrThrow())) {
|
||||||
|
scope.fork(StructuredDemo::fetchPrice);
|
||||||
|
scope.fork(StructuredDemo::fetchStock);
|
||||||
|
List<String> results = scope.join();
|
||||||
|
System.out.println("results = " + results);
|
||||||
|
Check.that(results.equals(List.of("price=42", "stock=7")), "join() returns the subtask results in fork order");
|
||||||
|
}
|
||||||
|
|
||||||
|
try (var scope = StructuredTaskScope.open(Joiner.<String>allSuccessfulOrThrow())) {
|
||||||
|
scope.fork(StructuredDemo::fetchPrice);
|
||||||
|
scope.fork(StructuredDemo::failingCall);
|
||||||
|
scope.join();
|
||||||
|
Check.that(false, "unreachable");
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
System.out.println("caught " + e.getClass().getName() + " with cause " + e.getCause());
|
||||||
|
Check.that(e.getCause() instanceof IllegalStateException, "a failing subtask surfaces as ExecutionException(cause)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** New in 27: hand the joiner a function and join() throws YOUR type. */
|
||||||
|
static void yourOwnExceptionType() throws InterruptedException {
|
||||||
|
System.out.println("--- allSuccessfulOrThrow(Function): join() throws the exception type you choose");
|
||||||
|
try (var scope = StructuredTaskScope.open(Joiner.<String, OrderFailed>allSuccessfulOrThrow(OrderFailed::new))) {
|
||||||
|
scope.fork(StructuredDemo::fetchPrice);
|
||||||
|
scope.fork(StructuredDemo::failingCall);
|
||||||
|
scope.join(); // declared: throws OrderFailed, InterruptedException
|
||||||
|
Check.that(false, "unreachable");
|
||||||
|
} catch (OrderFailed e) {
|
||||||
|
System.out.println("caught " + e.getClass().getSimpleName() + ": " + e.getMessage());
|
||||||
|
Check.that(e.getCause() instanceof IllegalStateException, "the mapped exception wraps the original failure");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Timeouts are now a configuration option; a Joiner decides what a timeout means. */
|
||||||
|
static void timeout() throws Exception {
|
||||||
|
System.out.println("--- withTimeout(...): what happens when the scope runs out of time");
|
||||||
|
try (var scope = StructuredTaskScope.open(Joiner.<String>allSuccessfulOrThrow(),
|
||||||
|
cfg -> cfg.withTimeout(Duration.ofMillis(100)))) {
|
||||||
|
Subtask<String> slow = scope.fork(() -> {
|
||||||
|
Thread.sleep(2_000);
|
||||||
|
return "too late";
|
||||||
|
});
|
||||||
|
scope.join();
|
||||||
|
Check.that(false, "unreachable");
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("caught " + e.getClass().getName());
|
||||||
|
System.out.println(" cause: " + e.getCause());
|
||||||
|
Check.that(e.getCause() instanceof StructuredTaskScope.CancelledByTimeoutException
|
||||||
|
|| e instanceof StructuredTaskScope.CancelledByTimeoutException,
|
||||||
|
"a timeout is reported as CancelledByTimeoutException");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import javax.net.ssl.SSLServerSocket;
|
||||||
|
import javax.net.ssl.SSLServerSocketFactory;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One end of a loopback TLS 1.3 handshake, so two different JDKs can be pointed at each other.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* java TlsPeer server <portfile> listen on an ephemeral port, write the port to a file, serve one handshake
|
||||||
|
* java TlsPeer client <port> connect and complete a handshake
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* The scripts run both with -Djavax.net.debug=ssl:handshake and read the negotiated key-exchange group
|
||||||
|
* out of the debug log; no JDK API reports it directly. Explained in docs/04-post-quantum-tls.md.
|
||||||
|
*/
|
||||||
|
public class TlsPeer {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
if (args[0].equals("server")) {
|
||||||
|
SSLServerSocket server = (SSLServerSocket) SSLServerSocketFactory.getDefault()
|
||||||
|
.createServerSocket(0, 1, InetAddress.getLoopbackAddress());
|
||||||
|
Files.writeString(Path.of(args[1]), Integer.toString(server.getLocalPort()));
|
||||||
|
try (SSLSocket socket = (SSLSocket) server.accept()) {
|
||||||
|
socket.startHandshake();
|
||||||
|
socket.getOutputStream().write('!');
|
||||||
|
}
|
||||||
|
server.close();
|
||||||
|
} else {
|
||||||
|
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault()
|
||||||
|
.createSocket(InetAddress.getLoopbackAddress(), Integer.parseInt(args[1]));
|
||||||
|
socket.startHandshake();
|
||||||
|
System.out.println("handshake complete: " + socket.getSession().getProtocol()
|
||||||
|
+ " " + socket.getSession().getCipherSuite());
|
||||||
|
InputStream in = socket.getInputStream();
|
||||||
|
in.read();
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import jdk.incubator.vector.FloatVector;
|
||||||
|
import jdk.incubator.vector.VectorOperators;
|
||||||
|
import jdk.incubator.vector.VectorSpecies;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JEP 537, Vector API (twelfth incubator). The same source and the same class file run on JDK 26
|
||||||
|
* (eleventh incubator, JEP 529) and JDK 27, because the public API did not change between them:
|
||||||
|
* docs/output/50-vector-api-surface.txt is the javap diff that shows it. For what the API is and
|
||||||
|
* how to use it well, see the companion guide to JEP 537 on ankurm.com.
|
||||||
|
* Run with --add-modules jdk.incubator.vector.
|
||||||
|
*/
|
||||||
|
public class VectorDemo {
|
||||||
|
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
|
||||||
|
|
||||||
|
static float scalarDot(float[] a, float[] b) {
|
||||||
|
float sum = 0;
|
||||||
|
for (int i = 0; i < a.length; i++) {
|
||||||
|
sum += a[i] * b[i];
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float vectorDot(float[] a, float[] b) {
|
||||||
|
int i = 0;
|
||||||
|
FloatVector acc = FloatVector.zero(SPECIES);
|
||||||
|
int bound = SPECIES.loopBound(a.length);
|
||||||
|
for (; i < bound; i += SPECIES.length()) {
|
||||||
|
acc = FloatVector.fromArray(SPECIES, a, i).fma(FloatVector.fromArray(SPECIES, b, i), acc);
|
||||||
|
}
|
||||||
|
float sum = acc.reduceLanes(VectorOperators.ADD);
|
||||||
|
for (; i < a.length; i++) { // the tail the vector loop could not cover
|
||||||
|
sum += a[i] * b[i];
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println("java.version = " + System.getProperty("java.version"));
|
||||||
|
System.out.println("preferred species = " + SPECIES + " (" + SPECIES.length() + " float lanes, machine dependent)");
|
||||||
|
int n = 1_000_003; // deliberately not a multiple of the lane count
|
||||||
|
float[] a = new float[n], b = new float[n];
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
a[i] = (i % 7) * 0.5f;
|
||||||
|
b[i] = (i % 5) * 0.25f;
|
||||||
|
}
|
||||||
|
float scalar = scalarDot(a, b);
|
||||||
|
float vector = vectorDot(a, b);
|
||||||
|
System.out.println("scalar dot = " + scalar);
|
||||||
|
System.out.println("vector dot = " + vector);
|
||||||
|
// Floating-point addition is not associative, so the two sums differ in the last bits; compare with a tolerance.
|
||||||
|
Check.that(Math.abs(scalar - vector) / Math.abs(scalar) < 1e-3, "vector and scalar dot products agree to within 0.1%");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* A primitive pattern placed after the reference pattern that already matches every Integer.
|
||||||
|
* The compiler rejects it on both 26 and 27. Driven by scripts/broken-on-27.sh.
|
||||||
|
*/
|
||||||
|
public class Dominated {
|
||||||
|
static String describe(Object o) {
|
||||||
|
return switch (o) {
|
||||||
|
case Integer boxed -> "an Integer";
|
||||||
|
case int primitive -> "an int"; // dominated: every Integer is already caught above
|
||||||
|
default -> "something else";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import java.lang.LazyConstant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JDK 26 code for the lazy constants preview. It compiles on 26 and fails on 27:
|
||||||
|
* LazyConstant.orElse(...) and LazyConstant.isInitialized() were removed in the third preview.
|
||||||
|
* Driven by scripts/broken-on-27.sh.
|
||||||
|
*/
|
||||||
|
public class Lazy26 {
|
||||||
|
static final LazyConstant<String> GREETING = LazyConstant.of(() -> "hello");
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
if (!GREETING.isInitialized()) {
|
||||||
|
System.out.println("not yet: " + GREETING.orElse("<unset>"));
|
||||||
|
}
|
||||||
|
System.out.println(GREETING.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import java.security.DEREncodable;
|
||||||
|
import java.security.PEM;
|
||||||
|
import java.security.PEMDecoder;
|
||||||
|
import java.security.Provider;
|
||||||
|
import java.security.Security;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JDK 26 code for the PEM preview API. It compiles on 26 and fails on 27.
|
||||||
|
* Three things moved between the second and third preview (JEP 524 -> JEP 538):
|
||||||
|
* 1. DEREncodable was renamed BinaryEncodable
|
||||||
|
* 2. PEMDecoder.withFactory(Provider) became withFactoriesOf(Provider)
|
||||||
|
* 3. PEM was a record whose content() returned a String; it is now a class whose content() returns byte[]
|
||||||
|
* Driven by scripts/broken-on-27.sh, which commits the compiler's own messages.
|
||||||
|
*/
|
||||||
|
public class Pem26Style {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
String text = "-----BEGIN ANKURM DEMO-----\naGVsbG8=\n-----END ANKURM DEMO-----\n";
|
||||||
|
DEREncodable decoded = PEMDecoder.of().decode(text); // 1
|
||||||
|
Provider provider = Security.getProviders()[0];
|
||||||
|
PEMDecoder viaProvider = PEMDecoder.of().withFactory(provider); // 2
|
||||||
|
if (decoded instanceof PEM pem) {
|
||||||
|
String base64Text = pem.content(); // 3
|
||||||
|
System.out.println(pem.type() + " / " + base64Text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.StructuredTaskScope;
|
||||||
|
import java.util.concurrent.StructuredTaskScope.Joiner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JDK 26 code for the structured concurrency preview. It compiles on 26 and fails on 27:
|
||||||
|
* StructuredTaskScope gained a third type parameter (the exception join() throws), and
|
||||||
|
* FailedException / TimeoutException no longer exist. Driven by scripts/broken-on-27.sh.
|
||||||
|
*/
|
||||||
|
public class StructuredScope26 {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
try (StructuredTaskScope<String, List<String>> scope =
|
||||||
|
StructuredTaskScope.open(Joiner.<String>allSuccessfulOrThrow())) {
|
||||||
|
scope.fork(() -> "price=42");
|
||||||
|
List<String> results = scope.join();
|
||||||
|
System.out.println(results);
|
||||||
|
} catch (StructuredTaskScope.FailedException e) {
|
||||||
|
System.out.println("a subtask failed: " + e.getCause());
|
||||||
|
} catch (StructuredTaskScope.TimeoutException e) {
|
||||||
|
System.out.println("timed out");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.openjdk.jol.info.ClassLayout;
|
||||||
|
import org.openjdk.jol.info.GraphLayout;
|
||||||
|
import org.openjdk.jol.vm.VM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JEP 534 seen from the inside: how many bytes does each object spend on its header, and what
|
||||||
|
* does that add up to across a million objects?
|
||||||
|
*
|
||||||
|
* <p>Uses JOL 0.17 ({@code org.openjdk.jol:jol-core}). Explained in docs/03-compact-headers.md.
|
||||||
|
* Run by scripts/object-headers.sh, once per JDK.
|
||||||
|
*/
|
||||||
|
public class HeaderDemo {
|
||||||
|
|
||||||
|
/** Two ints. The smallest object that is not empty. */
|
||||||
|
static class Point {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A more typical mix: a long, an int, a boolean and a reference. */
|
||||||
|
static class Order {
|
||||||
|
long id;
|
||||||
|
int quantity;
|
||||||
|
boolean paid;
|
||||||
|
String customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println("java.version = " + System.getProperty("java.version"));
|
||||||
|
System.out.println(VM.current().details());
|
||||||
|
|
||||||
|
section("java.lang.Object");
|
||||||
|
System.out.println(ClassLayout.parseClass(Object.class).toPrintable());
|
||||||
|
|
||||||
|
section("Point { int x; int y; }");
|
||||||
|
System.out.println(ClassLayout.parseClass(Point.class).toPrintable());
|
||||||
|
|
||||||
|
section("Order { long id; int quantity; boolean paid; String customer; }");
|
||||||
|
System.out.println(ClassLayout.parseClass(Order.class).toPrintable());
|
||||||
|
|
||||||
|
section("java.lang.Long and java.lang.Integer (boxed values)");
|
||||||
|
System.out.println(ClassLayout.parseInstance(Long.valueOf(123_456_789L)).toPrintable());
|
||||||
|
System.out.println(ClassLayout.parseInstance(Integer.valueOf(123_456)).toPrintable());
|
||||||
|
|
||||||
|
section("int[3]");
|
||||||
|
System.out.println(ClassLayout.parseInstance(new int[3]).toPrintable());
|
||||||
|
|
||||||
|
section("What it adds up to: one million objects");
|
||||||
|
List<Integer> boxed = new ArrayList<>();
|
||||||
|
List<Long> longs = new ArrayList<>();
|
||||||
|
List<Point> points = new ArrayList<>();
|
||||||
|
for (int i = 0; i < 1_000_000; i++) {
|
||||||
|
boxed.add(1_000 + i); // above the Integer cache, so every one is a real object
|
||||||
|
longs.add(10_000_000_000L + i);
|
||||||
|
Point p = new Point();
|
||||||
|
p.x = i;
|
||||||
|
p.y = -i;
|
||||||
|
points.add(p);
|
||||||
|
}
|
||||||
|
System.out.printf("1,000,000 boxed Integer values in an ArrayList : %,d bytes%n",
|
||||||
|
GraphLayout.parseInstance(boxed).totalSize());
|
||||||
|
System.out.printf("1,000,000 boxed Long values in an ArrayList : %,d bytes%n",
|
||||||
|
GraphLayout.parseInstance(longs).totalSize());
|
||||||
|
System.out.printf("1,000,000 Point objects in an ArrayList : %,d bytes%n",
|
||||||
|
GraphLayout.parseInstance(points).totalSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void section(String title) {
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("=== " + title);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import org.openjdk.jol.info.ClassLayout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JOL 0.17 cannot lay out a record out of the box: it asks sun.misc.Unsafe for field offsets, and
|
||||||
|
* Unsafe refuses for records. -Djol.magicFieldOffset=true switches JOL to a slower workaround.
|
||||||
|
* Run by scripts/object-headers.sh; both outcomes are committed as transcripts.
|
||||||
|
*/
|
||||||
|
public class RecordProbe {
|
||||||
|
record Coordinates(int lat, int lon) {}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(ClassLayout.parseClass(Coordinates.class).toPrintable());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.MathContext;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Small additions to the JDK 27 public API found by diffing javap dumps of 26 and 27
|
||||||
|
* (docs/output/51-api-diff-26-to-27.txt), each exercised once. Compile with --release 27.
|
||||||
|
*/
|
||||||
|
public class Api27 {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
System.out.printf("Math.acosh(2) = %.6f%n", Math.acosh(2));
|
||||||
|
System.out.printf("Math.asinh(1) = %.6f%n", Math.asinh(1));
|
||||||
|
System.out.printf("Math.atanh(.5) = %.6f%n", Math.atanh(0.5));
|
||||||
|
Check.that(Math.abs(Math.acosh(Math.cosh(1.5)) - 1.5) < 1e-12, "acosh inverts cosh");
|
||||||
|
|
||||||
|
String s = "na\u00efve caf\u00e9";
|
||||||
|
int utf8 = s.encodedLength(StandardCharsets.UTF_8);
|
||||||
|
System.out.println("\"" + s + "\".length() = " + s.length() + ", encodedLength(UTF_8) = " + utf8);
|
||||||
|
Check.that(utf8 == s.getBytes(StandardCharsets.UTF_8).length, "encodedLength matches getBytes().length without allocating the array");
|
||||||
|
|
||||||
|
BigDecimal cube = new BigDecimal("27").rootn(3, MathContext.DECIMAL64);
|
||||||
|
System.out.println("BigDecimal(27).rootn(3) = " + cube);
|
||||||
|
Check.that(cube.compareTo(new BigDecimal("3")) == 0, "rootn(3) of 27 is 3");
|
||||||
|
|
||||||
|
KeyStore ks = KeyStore.getInstance("PKCS12");
|
||||||
|
ks.load(null, null);
|
||||||
|
char[] pw = "changeit".toCharArray();
|
||||||
|
ks.setEntry("aes-key", new KeyStore.SecretKeyEntry(new SecretKeySpec(new byte[16], "AES")),
|
||||||
|
new KeyStore.PasswordProtection(pw));
|
||||||
|
Instant created = ks.getCreationInstant("aes-key");
|
||||||
|
System.out.println("KeyStore.getCreationInstant(\"aes-key\") returns an Instant: " + (created != null));
|
||||||
|
Check.that(created != null && Math.abs(created.toEpochMilli() - System.currentTimeMillis()) < 60_000,
|
||||||
|
"KeyStore.getCreationInstant returns the entry's creation time as a java.time.Instant");
|
||||||
|
|
||||||
|
Set<Integer> evens = Set.ofLazy(Set.of(1, 2, 3, 4), n -> n % 2 == 0);
|
||||||
|
Check.that(evens.contains(2) && !evens.contains(3), "Set.ofLazy(Set, Predicate) exists (preview API)");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Tiny assertion helper so every demo is self-checking: a claim that stops being true turns the
|
||||||
|
* run red instead of quietly printing something different. Passing checks are echoed, so the
|
||||||
|
* transcript shows exactly what was verified.
|
||||||
|
*/
|
||||||
|
final class Check {
|
||||||
|
private Check() {}
|
||||||
|
|
||||||
|
static void that(boolean condition, String claim) {
|
||||||
|
if (!condition) {
|
||||||
|
throw new AssertionError("CHECK FAILED: " + claim);
|
||||||
|
}
|
||||||
|
System.out.println("CHECK ok : " + claim);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A behaviour change from the JDK 27 release notes (JDK-8355522) that needs no new API to trigger, so the
|
||||||
|
* same class file can run on 26 and 27 and show the difference. Run by scripts/other-changes.sh.
|
||||||
|
*/
|
||||||
|
public class Compat {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println("java.version = " + System.getProperty("java.version"));
|
||||||
|
System.out.println("Locale(\"iw\").language = " + Locale.forLanguageTag("iw").getLanguage()
|
||||||
|
+ " (Hebrew; old code 'iw', current code 'he')");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/** Starts one child process. JDK 27 removed the VFORK launch mechanism, so a script that pinned it now fails here. */
|
||||||
|
public class SpawnProbe {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
Process p = new ProcessBuilder("true").start();
|
||||||
|
System.out.println("child exited with " + p.waitFor());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import java.time.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.*;
|
||||||
|
public class AotApp {
|
||||||
|
public static void main(String[] a) {
|
||||||
|
long t0 = System.nanoTime();
|
||||||
|
var m = IntStream.range(0, 2000).boxed().collect(Collectors.groupingBy(i -> i % 7, TreeMap::new, Collectors.summingInt(i -> i)));
|
||||||
|
String s = String.format("%s %s %s", m, LocalDate.of(2026, 9, 21).getDayOfWeek(), Duration.ofSeconds(90));
|
||||||
|
System.out.println(s.length() > 10 ? "ok" : "no");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public API that first appeared in Java 26 and needs no --enable-preview. Each call is asserted, so the
|
||||||
|
* run is the proof. The list was produced by diffing javap dumps of 25 and 26 (docs/output/50-*).
|
||||||
|
* Chapter: docs/11-recap-26.md
|
||||||
|
*/
|
||||||
|
public class Api26 {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
// String: Unicode case-folding comparisons, no more toLowerCase(Locale.ROOT) allocations
|
||||||
|
Check.that("STRASSE".equalsFoldCase("straße"), "\"STRASSE\".equalsFoldCase(\"stra\" + sharp s + \"e\") is true (full case folding)");
|
||||||
|
Check.that(!"STRASSE".equalsIgnoreCase("straße"), "equalsIgnoreCase says false for the same pair (simple folding only)");
|
||||||
|
Check.that("apple".compareToFoldCase("BANANA") < 0, "\"apple\".compareToFoldCase(\"BANANA\") < 0");
|
||||||
|
List<String> names = new java.util.ArrayList<>(List.of("b", "A", "c", "B"));
|
||||||
|
names.sort(String.UNICODE_CASEFOLD_ORDER);
|
||||||
|
Check.that(names.get(0).equalsFoldCase("a") && names.get(2).equalsFoldCase("b"), "sorted with String.UNICODE_CASEFOLD_ORDER: " + names);
|
||||||
|
|
||||||
|
// UUID: a v7-shaped UUID for a given instant
|
||||||
|
UUID u = UUID.ofEpochMillis(1_700_000_000_000L);
|
||||||
|
Check.that(u.version() == 7, "UUID.ofEpochMillis(..).version() == 7");
|
||||||
|
Check.that(u.toString().startsWith("018bcfe5-6800"), "the first 48 bits are the timestamp: " + u.toString().substring(0, 13) + "-...");
|
||||||
|
|
||||||
|
// Process is AutoCloseable now
|
||||||
|
try (Process p = new ProcessBuilder("true").start()) {
|
||||||
|
Check.that(p.waitFor() == 0, "try-with-resources on Process (implements Closeable in 26)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemoryMXBean: cumulative CPU time spent in GC
|
||||||
|
long gcCpu = ManagementFactory.getMemoryMXBean().getTotalGcCpuTime();
|
||||||
|
Check.that(gcCpu >= 0, "MemoryMXBean.getTotalGcCpuTime() exists and is non-negative");
|
||||||
|
|
||||||
|
// ByteOrder became an enum
|
||||||
|
Check.that(ByteOrder.BIG_ENDIAN instanceof Enum<?>, "ByteOrder is an enum: " + java.util.Arrays.toString(ByteOrder.values()));
|
||||||
|
|
||||||
|
// HTTP/3 in the client API
|
||||||
|
HttpClient c = HttpClient.newBuilder().version(HttpClient.Version.HTTP_3).build();
|
||||||
|
Check.that(c.version() == HttpClient.Version.HTTP_3, "HttpClient.Version.HTTP_3 exists and is accepted by the builder");
|
||||||
|
Check.that(Character.UnicodeBlock.SIDETIC != null, "Character.UnicodeBlock.SIDETIC exists (Unicode 17)");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Tiny assertion helper so every demo is self-checking: a claim that stops being true turns the
|
||||||
|
* run red instead of quietly printing something different. Passing checks are echoed, so the
|
||||||
|
* transcript shows exactly what was verified.
|
||||||
|
*/
|
||||||
|
final class Check {
|
||||||
|
private Check() {}
|
||||||
|
|
||||||
|
static void that(boolean condition, String claim) {
|
||||||
|
if (!condition) {
|
||||||
|
throw new AssertionError("CHECK FAILED: " + claim);
|
||||||
|
}
|
||||||
|
System.out.println("CHECK ok : " + claim);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JEP 500 (Java 26): "Prepare to Make Final Mean Final". Deep reflection on a final field
|
||||||
|
* (Field.setAccessible(true) followed by Field.setInt) used to work silently. On 26 it works but
|
||||||
|
* warns once; a future release will deny it unless you opt in.
|
||||||
|
*
|
||||||
|
* The same file is run five ways by scripts/recap26.sh; the transcript is docs/output/70-final-field-mutation.txt.
|
||||||
|
* Chapter: docs/11-recap-26.md
|
||||||
|
*/
|
||||||
|
public class FinalFieldMutation {
|
||||||
|
static class Config {
|
||||||
|
private final int port = 8080;
|
||||||
|
int port() { return port; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
Config c = new Config();
|
||||||
|
Field f = Config.class.getDeclaredField("port");
|
||||||
|
f.setAccessible(true);
|
||||||
|
try {
|
||||||
|
f.setInt(c, 9090);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
System.out.println("setInt refused : " + e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// The field reads 9090 but port() still says 8080: 'final int port = 8080' is a constant variable, so javac
|
||||||
|
// copied the literal 8080 into port() at compile time. Nothing you write to the field can reach that copy.
|
||||||
|
// That silent disagreement is the reason the platform wants this to stop, not a hypothetical.
|
||||||
|
System.out.println("field reads : " + f.getInt(c));
|
||||||
|
System.out.println("port() returns : " + c.port());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import com.sun.net.httpserver.HttpsConfigurator;
|
||||||
|
import com.sun.net.httpserver.HttpsServer;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpOption;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JEP 517 (Java 26): HTTP/3 for the HTTP Client API. The sandbox cannot reach a real HTTP/3 server (no UDP egress),
|
||||||
|
* so this shows the half that is checkable everywhere: a client that PREFERS HTTP/3 talking to a server that does not
|
||||||
|
* speak it (the JDK's own HttpsServer, HTTP/1.1) quietly falls back, and says so in response.version().
|
||||||
|
* args: keystore path, password
|
||||||
|
* Chapter: docs/11-recap-26.md
|
||||||
|
*/
|
||||||
|
public class Http3Fallback {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
KeyStore ks = KeyStore.getInstance("PKCS12");
|
||||||
|
try (var in = new FileInputStream(args[0])) { ks.load(in, args[1].toCharArray()); }
|
||||||
|
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(ks, args[1].toCharArray());
|
||||||
|
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); tmf.init(ks);
|
||||||
|
SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
|
||||||
|
|
||||||
|
HttpsServer server = HttpsServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
|
||||||
|
server.setHttpsConfigurator(new HttpsConfigurator(ctx));
|
||||||
|
server.createContext("/", ex -> { byte[] b = "hello".getBytes(); ex.sendResponseHeaders(200, b.length); ex.getResponseBody().write(b); ex.close(); });
|
||||||
|
server.start();
|
||||||
|
int port = server.getAddress().getPort();
|
||||||
|
try (HttpClient client = HttpClient.newBuilder().sslContext(ctx).version(HttpClient.Version.HTTP_3).build()) {
|
||||||
|
HttpRequest req = HttpRequest.newBuilder(URI.create("https://localhost:" + port + "/")).GET().build();
|
||||||
|
HttpResponse<String> r = client.send(req, HttpResponse.BodyHandlers.ofString());
|
||||||
|
System.out.println("client asked for : " + client.version());
|
||||||
|
System.out.println("server answered : " + r.version() + " " + r.statusCode() + " " + r.body());
|
||||||
|
|
||||||
|
HttpRequest strict = HttpRequest.newBuilder(URI.create("https://localhost:" + port + "/")).GET()
|
||||||
|
.setOption(HttpOption.H3_DISCOVERY, HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY).build();
|
||||||
|
try {
|
||||||
|
client.send(strict, HttpResponse.BodyHandlers.ofString());
|
||||||
|
System.out.println("HTTP_3_URI_ONLY : unexpectedly succeeded");
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("HTTP_3_URI_ONLY : " + e.getClass().getName() + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
server.stop(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// JEP 504 (Java 26) removed java.applet.*, javax.swing.JApplet and the AppletInitializer overload in java.beans.
|
||||||
|
// On 25 this compiles with deprecation warnings. On 26 it does not compile.
|
||||||
|
import java.applet.Applet;
|
||||||
|
|
||||||
|
public class AppletGone extends Applet {
|
||||||
|
@Override public void init() { System.out.println("applet init"); }
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// Thread.stop() has thrown UnsupportedOperationException since 20. In 26 the method itself is gone.
|
||||||
|
public class ThreadStopGone {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Thread t = new Thread(() -> {});
|
||||||
|
t.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+17
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Public-API diffs between JDK 25 (LTS), 26 and 27, produced from javap dumps of every exported class.
|
||||||
|
# ./scripts/api-diff.sh -> docs/output/50-vector-api-surface.txt, 51-api-diff-26-to-27.txt, 52-api-diff-25-to-26.txt
|
||||||
|
# The dumps are 5 MB each and take a few minutes to build, so they are cached in build/apidiff/.
|
||||||
|
set -euo pipefail
|
||||||
|
source "$(dirname "$0")/env.sh"
|
||||||
|
D="$ROOT/build/apidiff"; mkdir -p "$D"
|
||||||
|
for v in 25 26 27; do
|
||||||
|
jdk="JDK$v"
|
||||||
|
[ -s "$D/api-$v.txt" ] || python3 "$ROOT/api-diff/dump_api.py" "${!jdk}" "$D/api-$v.txt"
|
||||||
|
done
|
||||||
|
{ echo "# Public API removed/added/changed between JDK 26 and 27 (javap -public of every exported class; internal packages skipped)"; echo
|
||||||
|
python3 "$ROOT/api-diff/apidiff.py" "$D/api-26.txt" "$D/api-27.txt"; } > "$OUT/51-api-diff-26-to-27.txt"
|
||||||
|
{ echo "# Public API removed/added/changed between JDK 25 and 26"; echo
|
||||||
|
python3 "$ROOT/api-diff/apidiff.py" "$D/api-25.txt" "$D/api-26.txt"; } > "$OUT/52-api-diff-25-to-26.txt"
|
||||||
|
{ echo "# jdk.incubator.vector: what changed between 26 and 27 (JEP 529 was the 11th incubation in 26, JEP 537 is the 12th in 27)"; echo
|
||||||
|
python3 "$ROOT/api-diff/apidiff.py" "$D/api-26.txt" "$D/api-27.txt" --vector-only; } > "$OUT/50-vector-api-surface.txt"
|
||||||
Executable
+32
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# The most useful demonstrations of a preview API are the compile errors. This compiles the same
|
||||||
|
# JDK 26 sources with the 26 and the 27 compiler and commits what javac actually said.
|
||||||
|
# ./scripts/broken-on-27.sh -> docs/output/20-broken-*.txt
|
||||||
|
set -uo pipefail
|
||||||
|
source "$(dirname "$0")/env.sh"
|
||||||
|
SRC="$ROOT/jep-tour/src"
|
||||||
|
|
||||||
|
compile() { # jdk-home release file -> prints javac's own output, then its real exit code
|
||||||
|
local out rc
|
||||||
|
out="$( cd "$SRC/broken" && "$1/bin/javac" --enable-preview --release "$2" -Xlint:-preview -d "$ROOT/build/broken-$2" "$3" 2>&1 )"; rc=$?
|
||||||
|
printf '%s\n' "$out" | grep -v -E '^(Note: |$)' || true
|
||||||
|
echo "exit=$rc"
|
||||||
|
}
|
||||||
|
|
||||||
|
for f in Pem26Style StructuredScope26 Lazy26; do
|
||||||
|
out="$OUT/20-broken-$(echo "$f" | tr 'A-Z' 'a-z').txt"
|
||||||
|
{
|
||||||
|
echo "\$ javac --enable-preview --release 26 $f.java (JDK 26)"
|
||||||
|
compile "$JDK26" 26 "$f.java"
|
||||||
|
echo
|
||||||
|
echo "\$ javac --enable-preview --release 27 $f.java (JDK 27)"
|
||||||
|
compile "$JDK27" 27 "$f.java"
|
||||||
|
} > "$out" 2>&1
|
||||||
|
echo "wrote $out"
|
||||||
|
done
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "\$ javac --enable-preview --release 27 Dominated.java (JDK 27)"
|
||||||
|
compile "$JDK27" 27 Dominated.java
|
||||||
|
} > "$OUT/20-broken-dominated.txt" 2>&1
|
||||||
|
echo "wrote $OUT/20-broken-dominated.txt"
|
||||||
Executable
+14
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Where the JDKs live. Override on the command line: JDK27=/path/to/jdk-27 ./scripts/run-all.sh
|
||||||
|
# Every demo that compares releases needs BOTH a JDK 26 and a JDK 27 on disk.
|
||||||
|
export JDK25="${JDK25:-/opt/jdks/jdk-25.0.4.1+1}"
|
||||||
|
export JDK26="${JDK26:-/opt/jdks/jdk26}"
|
||||||
|
export JDK27="${JDK27:-/opt/jdks/jdk27}"
|
||||||
|
# Container demos use these images (same vendor for both releases, so the only variable is the JDK version).
|
||||||
|
export IMG26="${IMG26:-amazoncorretto:26}"
|
||||||
|
export IMG27="${IMG27:-amazoncorretto:27}"
|
||||||
|
# The JVM prints "Picked up JAVA_TOOL_OPTIONS" on every launch in some sandboxes; it is noise in a transcript.
|
||||||
|
unset JAVA_TOOL_OPTIONS
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
export ROOT
|
||||||
|
OUT="$ROOT/docs/output"; mkdir -p "$OUT"; export OUT
|
||||||
Executable
+25
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# What does the switch cost on a machine that used to get Serial? 1 CPU, 2 GB, three runs each.
|
||||||
|
# Timing is indicative only: one shared machine, one process per run. Read the SHAPE, not the digits.
|
||||||
|
# ./scripts/g1-cost.sh -> docs/output/04-g1-cost-1cpu.txt (4000 batches, ~6-8 s per run)
|
||||||
|
# BATCHES=400 OUTNAME=05-g1-cost-1cpu-short.txt ./scripts/g1-cost.sh (a short-lived process, e.g. a CI job)
|
||||||
|
set -euo pipefail
|
||||||
|
source "$(dirname "$0")/env.sh"
|
||||||
|
BUILD="$ROOT/build/g1"; mkdir -p "$BUILD"
|
||||||
|
"$JDK27/bin/javac" --release 26 -d "$BUILD" "$ROOT"/g1-container/src/*.java
|
||||||
|
RUNS="${RUNS:-3}"; BATCHES="${BATCHES:-4000}"; OUTNAME="${OUTNAME:-04-g1-cost-1cpu.txt}"
|
||||||
|
{
|
||||||
|
echo "# Allocation workload, 1 CPU / 2 GB container, $BATCHES batches x 50,000 objects, $RUNS runs per configuration (indicative, not a benchmark)"
|
||||||
|
echo "# 'default' means nobody passed a collector flag."
|
||||||
|
echo
|
||||||
|
for cfg in "$IMG26|" "$IMG27|" "$IMG27|-XX:+UseSerialGC"; do
|
||||||
|
img="${cfg%%|*}"; flag="${cfg##*|}"
|
||||||
|
for i in $(seq 1 "$RUNS"); do
|
||||||
|
echo "=== $img ${flag:-default} run $i"
|
||||||
|
docker run --rm --cpus=1 --memory=2g -v "$BUILD:/app:ro" "$img" \
|
||||||
|
java $flag -cp /app Workload "$BATCHES" | grep -v -E '^allocated'
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
done
|
||||||
|
} > "$OUT/$OUTNAME" 2>&1
|
||||||
|
cat "$OUT/$OUTNAME"
|
||||||
Executable
+49
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# JEP 523 in a real container: what does the JVM pick when nobody asks for a collector?
|
||||||
|
# Runs GcReport in Docker with 1 CPU and 2 CPUs, on the JDK 26 and JDK 27 images.
|
||||||
|
# ./scripts/g1-default.sh -> docs/output/01-g1-default-1cpu-vs-2cpu.txt
|
||||||
|
# docs/output/02-g1-default-memory.txt
|
||||||
|
set -euo pipefail
|
||||||
|
source "$(dirname "$0")/env.sh"
|
||||||
|
BUILD="$ROOT/build/g1"; rm -rf "$BUILD"; mkdir -p "$BUILD"
|
||||||
|
"$JDK27/bin/javac" --release 26 -d "$BUILD" "$ROOT"/g1-container/src/*.java # one class file set runs on both JDKs
|
||||||
|
|
||||||
|
run() { # image label docker-args... -- java-args...
|
||||||
|
local img="$1" label="$2"; shift 2
|
||||||
|
echo "\$ docker run --rm $* $img java -cp /app GcReport"
|
||||||
|
docker run --rm "$@" -v "$BUILD:/app:ro" "$img" java -cp /app GcReport
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# JEP 523: which collector does the JVM pick when you do not choose one?"
|
||||||
|
echo "# Same class files, same vendor (Amazon Corretto), same 2 GB memory limit. Only the JDK version and the CPU limit change."
|
||||||
|
echo
|
||||||
|
for cpus in 1 2; do
|
||||||
|
for img in "$IMG26" "$IMG27"; do
|
||||||
|
echo "=== $img --cpus=$cpus --memory=2g"
|
||||||
|
run "$img" x --cpus=$cpus --memory=2g
|
||||||
|
done
|
||||||
|
done
|
||||||
|
} > "$OUT/01-g1-default-1cpu-vs-2cpu.txt" 2>&1
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# The other half of the old rule: memory. The old heuristic wanted at least 2 CPUs AND about 1792 MB."
|
||||||
|
echo "# With 2 CPUs the CPU half of the rule is satisfied, so only the memory limit differs between these runs."
|
||||||
|
echo
|
||||||
|
for mem in 1g 2g; do
|
||||||
|
for img in "$IMG26" "$IMG27"; do
|
||||||
|
echo "=== $img --cpus=2 --memory=$mem"
|
||||||
|
run "$img" x --cpus=2 --memory=$mem
|
||||||
|
done
|
||||||
|
done
|
||||||
|
} > "$OUT/02-g1-default-memory.txt" 2>&1
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# The opt-out still works on 27: an explicit choice always wins over the JVM's."
|
||||||
|
echo
|
||||||
|
echo "=== $IMG27 --cpus=1 --memory=2g -XX:+UseSerialGC"
|
||||||
|
echo "\$ docker run --rm --cpus=1 --memory=2g $IMG27 java -XX:+UseSerialGC -cp /app GcReport"
|
||||||
|
docker run --rm --cpus=1 --memory=2g -v "$BUILD:/app:ro" "$IMG27" java -XX:+UseSerialGC -cp /app GcReport
|
||||||
|
} > "$OUT/03-g1-default-optout.txt" 2>&1
|
||||||
|
echo "wrote 01-, 02-, 03- transcripts to $OUT"
|
||||||
Executable
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Why does G1 on 1 CPU touch ~490 MB in a 0.5 s process? Two suspects, tested:
|
||||||
|
# 1. the JDK 27 change of MinHeapFreeRatio/MaxHeapFreeRatio from 40/70 to 0/100 -> restored here; no effect
|
||||||
|
# 2. G1 simply being allowed to grow toward the 512 MB default maximum heap -> capped here; RSS follows the cap
|
||||||
|
# ./scripts/g1-tuning.sh -> docs/output/06-g1-tuning-1cpu.txt
|
||||||
|
set -euo pipefail
|
||||||
|
source "$(dirname "$0")/env.sh"
|
||||||
|
BUILD="$ROOT/build/g1"; mkdir -p "$BUILD"
|
||||||
|
"$JDK27/bin/javac" --release 26 -d "$BUILD" "$ROOT"/g1-container/src/*.java
|
||||||
|
{
|
||||||
|
echo "# JDK 27, G1 (the default), 1 CPU / 2 GB container, 400 batches, 2 runs per configuration"
|
||||||
|
echo
|
||||||
|
echo "=== flags that differ between 26 and 27 (from -XX:+PrintFlagsFinal)"
|
||||||
|
for img in "$IMG26" "$IMG27"; do
|
||||||
|
echo "--- $img"
|
||||||
|
docker run --rm --cpus=1 --memory=2g "$img" java -XX:+PrintFlagsFinal -version 2>/dev/null \
|
||||||
|
| grep -E ' (MinHeapFreeRatio|MaxHeapFreeRatio|MaxHeapSize|ConcGCThreads|ParallelGCThreads|UseCompactObjectHeaders|G1IHOP|UseCompressedClassPointers) ' || true
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
for flags in "" "-XX:MinHeapFreeRatio=40 -XX:MaxHeapFreeRatio=70" "-Xmx256m"; do
|
||||||
|
echo "=== $IMG27 flags: [${flags:-none}]"
|
||||||
|
for i in 1 2; do
|
||||||
|
docker run --rm --cpus=1 --memory=2g -v "$BUILD:/app:ro" "$IMG27" java $flags -cp /app Workload 400 \
|
||||||
|
| grep -E 'wall|gc count|peak'
|
||||||
|
done
|
||||||
|
done
|
||||||
|
} > "$OUT/06-g1-tuning-1cpu.txt" 2>&1
|
||||||
|
cat "$OUT/06-g1-tuning-1cpu.txt"
|
||||||
Executable
+17
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Runs the five JDK 27 feature demos on a real JDK 27. Every demo asserts its own claims (see Check.java),
|
||||||
|
# so a green transcript means the sentence in the post is still true.
|
||||||
|
# ./scripts/jep-tour.sh -> docs/output/21-structured.txt 22-lazy.txt 23-primitive-patterns.txt 24-pem.txt 25-vector.txt
|
||||||
|
set -uo pipefail
|
||||||
|
source "$(dirname "$0")/env.sh"
|
||||||
|
S="$ROOT/jep-tour/src"; B="$ROOT/build/tour"; rm -rf "$B"; mkdir -p "$B"
|
||||||
|
"$JDK27/bin/javac" --enable-preview --release 27 -Xlint:-preview -d "$B" "$S/Check.java" "$S/StructuredDemo.java" "$S/LazyDemo.java" \
|
||||||
|
"$S/PrimitivePatterns.java" "$S/PemDemo.java" 2>&1 | grep -v '^Note:' || true
|
||||||
|
"$JDK27/bin/javac" --add-modules jdk.incubator.vector --release 27 -d "$B" "$S/Check.java" "$S/VectorDemo.java" 2>&1 | grep -v -E '^(Note:|WARNING)' || true
|
||||||
|
run() { local name="$1" cls="$2"; shift 2
|
||||||
|
{ echo "\$ java $* $cls (JDK 27)"; "$JDK27/bin/java" -Dstdout.encoding=UTF-8 "$@" -cp "$B" "$cls" 2>&1 | grep -v '^WARNING: Using incubator'; echo "exit=${PIPESTATUS[0]}"; } > "$OUT/$name" 2>&1; }
|
||||||
|
run 21-structured.txt StructuredDemo --enable-preview
|
||||||
|
run 22-lazy.txt LazyDemo --enable-preview
|
||||||
|
run 23-primitive-patterns.txt PrimitivePatterns --enable-preview
|
||||||
|
run 24-pem.txt PemDemo --enable-preview
|
||||||
|
run 25-vector.txt VectorDemo --add-modules jdk.incubator.vector
|
||||||
Executable
+34
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# JEP 536: JFR in-process data redaction. Start a JVM that has secrets in its arguments, system
|
||||||
|
# properties and environment, record it with JFR, and print what ended up in the recording.
|
||||||
|
# ./scripts/jfr-redaction.sh -> docs/output/40-jfr-redaction.txt
|
||||||
|
set -euo pipefail
|
||||||
|
source "$(dirname "$0")/env.sh"
|
||||||
|
B="$ROOT/build/jfr"; rm -rf "$B"; mkdir -p "$B"
|
||||||
|
"$JDK27/bin/javac" --release 26 -d "$B" "$ROOT/jep-tour/src/Idle.java"
|
||||||
|
|
||||||
|
# record <label> <jdk> <FlightRecorderOptions value or empty>
|
||||||
|
record() {
|
||||||
|
local label="$1" jdk="$2" opts="${3:-}" jfr="$B/$1.jfr"
|
||||||
|
local fropt=(); [ -n "$opts" ] && fropt=("-XX:FlightRecorderOptions:$opts")
|
||||||
|
DB_PASSWORD=hunter2 "$jdk/bin/java" -Dapi.token=abc123 -Dregion=ap-south-1 "${fropt[@]}" \
|
||||||
|
-XX:StartFlightRecording:filename="$jfr" -cp "$B" Idle --password=hunter2 --user=ankur >/dev/null 2>&1
|
||||||
|
echo "=== $label"
|
||||||
|
[ -n "$opts" ] && echo " -XX:FlightRecorderOptions:$opts"
|
||||||
|
"$jdk/bin/jfr" print --events jdk.JVMInformation "$jfr" | grep -E 'jvmArguments|javaArguments' | sed 's/^ *//' | sed -E 's/(-XX:StartFlightRecording:filename=)[^ "]*/\1<file>/'
|
||||||
|
"$jdk/bin/jfr" print --events jdk.InitialSystemProperty "$jfr" | grep -A1 -E 'key = "(api.token|region)"' | grep -v -- '^--$' | sed 's/^ *//' | paste - -
|
||||||
|
"$jdk/bin/jfr" print --events jdk.InitialEnvironmentVariable "$jfr" | grep -A1 -E 'key = "DB_PASSWORD"' | grep -v -- '^--$' | sed 's/^ *//' | paste - -
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# The process under test: java -Dapi.token=abc123 -Dregion=ap-south-1 ... Idle --password=hunter2 --user=ankur"
|
||||||
|
echo "# with DB_PASSWORD=hunter2 in the environment. Which of those values reach the recording?"
|
||||||
|
echo
|
||||||
|
record "jdk26-default" "$JDK26"
|
||||||
|
record "jdk27-default" "$JDK27"
|
||||||
|
record "jdk27-own-list-replaces-defaults" "$JDK27" "redact-argument=--user*,redact-key=region"
|
||||||
|
record "jdk27-plus-extends-defaults" "$JDK27" "redact-argument=+--user*,redact-key=+region"
|
||||||
|
record "jdk27-none-disables-redaction" "$JDK27" "redact-argument=none,redact-key=none"
|
||||||
|
} > "$OUT/40-jfr-redaction.txt" 2>&1
|
||||||
|
cat "$OUT/40-jfr-redaction.txt"
|
||||||
Executable
+40
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# JEP 534: object header size on JDK 26 (12 bytes) vs JDK 27 (8 bytes), measured with JOL 0.17.
|
||||||
|
# ./scripts/object-headers.sh -> docs/output/10-headers-jdk26.txt, 11-headers-jdk27.txt,
|
||||||
|
# 12-headers-jdk27-opt-out.txt, 13-jol-record-failure.txt,
|
||||||
|
# 14-jol-record-workaround.txt
|
||||||
|
set -euo pipefail
|
||||||
|
source "$(dirname "$0")/env.sh"
|
||||||
|
|
||||||
|
LIB="$ROOT/.lib"; mkdir -p "$LIB"
|
||||||
|
JOL="$LIB/jol-core-0.17.jar"
|
||||||
|
if [ ! -f "$JOL" ]; then
|
||||||
|
curl -sfL -o "$JOL" https://repo1.maven.org/maven2/org/openjdk/jol/jol-core/0.17/jol-core-0.17.jar
|
||||||
|
fi
|
||||||
|
echo "4c98e9e61b3f189241057cb21b4331d1093e5b85 $JOL" | sha1sum -c - >/dev/null # Maven Central's own .sha1
|
||||||
|
|
||||||
|
BUILD="$ROOT/build/headers"; rm -rf "$BUILD"; mkdir -p "$BUILD"
|
||||||
|
"$JDK27/bin/javac" --release 26 -cp "$JOL" -d "$BUILD" "$ROOT"/object-headers/src/*.java
|
||||||
|
|
||||||
|
# JOL wants to attach to itself (for Instrumentation) and calls sun.misc.Unsafe, which JDK 24+ warns about.
|
||||||
|
COMMON=(-Djdk.attach.allowAttachSelf=true -XX:+EnableDynamicAgentLoading --sun-misc-unsafe-memory-access=allow -cp "$BUILD:$JOL")
|
||||||
|
|
||||||
|
# The Serviceability Agent line JOL 0.17 prints on JDK 27 is JOL failing to read a flag that no longer exists
|
||||||
|
# (UseCompressedClassPointers is obsolete in 27); it is kept in the transcript because it is real.
|
||||||
|
"$JDK26/bin/java" "${COMMON[@]}" HeaderDemo > "$OUT/10-headers-jdk26.txt" 2>&1
|
||||||
|
"$JDK27/bin/java" "${COMMON[@]}" HeaderDemo > "$OUT/11-headers-jdk27.txt" 2>&1
|
||||||
|
"$JDK27/bin/java" -XX:-UseCompactObjectHeaders "${COMMON[@]}" HeaderDemo > "$OUT/12-headers-jdk27-opt-out.txt" 2>&1
|
||||||
|
|
||||||
|
# JOL 0.17 cannot lay out a record without a workaround. Capture the failure and the fix.
|
||||||
|
"$JDK27/bin/java" "${COMMON[@]}" RecordProbe > "$OUT/13-jol-record-failure.txt" 2>&1 || true
|
||||||
|
"$JDK27/bin/java" -Djol.magicFieldOffset=true "${COMMON[@]}" RecordProbe > "$OUT/14-jol-record-workaround.txt" 2>&1
|
||||||
|
|
||||||
|
# One compact table of instance sizes, extracted from the three transcripts (not retyped).
|
||||||
|
{
|
||||||
|
echo "# Instance sizes in bytes, extracted from 10-, 11- and 12- transcripts"
|
||||||
|
for f in 10-headers-jdk26 11-headers-jdk27 12-headers-jdk27-opt-out; do
|
||||||
|
echo "--- $f"
|
||||||
|
awk '/^=== /{t=$0} /^Instance size/{print t " -> " $0} /^1,000,000 /{print}' "$OUT/$f.txt"
|
||||||
|
done
|
||||||
|
} > "$OUT/15-headers-summary.txt"
|
||||||
|
cat "$OUT/15-headers-summary.txt"
|
||||||
Executable
+41
@@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# The JDK 27 changes that are not JEPs but will still break somebody's Monday: JVM options that no
|
||||||
|
# longer exist, a launch mechanism that is gone, a locale property that is ignored, new public APIs.
|
||||||
|
# ./scripts/other-changes.sh -> docs/output/60-removed-options.txt, 61-behaviour-changes.txt, 62-new-api.txt
|
||||||
|
set -uo pipefail
|
||||||
|
source "$(dirname "$0")/env.sh"
|
||||||
|
B="$ROOT/build/other"; rm -rf "$B"; mkdir -p "$B"
|
||||||
|
SRC="$ROOT/other-changes/src"
|
||||||
|
"$JDK27/bin/javac" --release 26 -d "$B" "$SRC/Compat.java" "$SRC/SpawnProbe.java"
|
||||||
|
"$JDK27/bin/javac" --enable-preview --release 27 -d "$B" "$SRC/Check.java" "$SRC/Api27.java" 2>&1 | grep -v '^Note' || true
|
||||||
|
|
||||||
|
# first two lines of the JVM's response to a launch option, and the exit code
|
||||||
|
try() { local jdk="$1"; shift; local out rc; out="$("$jdk/bin/java" "$@" -version 2>&1 | grep -v -E '^(openjdk version|OpenJDK Runtime|OpenJDK 64-Bit Server VM \()' | head -2)"; rc=${PIPESTATUS[0]}
|
||||||
|
[ -z "$out" ] && out="(accepted silently)"; printf '%s\n' "$out" | sed 's/^/ /'; }
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# JVM options that JDK 27 removed or renamed. Each runs 'java <option> -version' on 26 and on 27."
|
||||||
|
for opt in "-noverify" "-Xverify:none" "-noclassgc" "-XX:+UseCompressedClassPointers" \
|
||||||
|
"-XX:InitiatingHeapOccupancyPercent=40" "-XX:G1IHOP=40"; do
|
||||||
|
echo; echo "=== $opt"
|
||||||
|
for v in 26 27; do jdk="JDK$v"; echo " JDK $v:"; try "${!jdk}" $opt; done
|
||||||
|
done
|
||||||
|
echo; echo "=== -XX:+UnlockExperimentalVMOptions -XX:+UseGraalJIT (JVMCI and the Graal JIT hook were removed in 27)"
|
||||||
|
for v in 26 27; do jdk="JDK$v"; echo " JDK $v:"; try "${!jdk}" -XX:+UnlockExperimentalVMOptions -XX:+UseGraalJIT; done
|
||||||
|
} > "$OUT/60-removed-options.txt" 2>&1
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# Behaviour changes that need no new API to observe"
|
||||||
|
echo; echo "=== a script that pins the VFORK process launch mechanism"
|
||||||
|
for v in 26 27; do jdk="JDK$v"; echo " JDK $v: java -Djdk.lang.Process.launchMechanism=VFORK SpawnProbe"
|
||||||
|
"${!jdk}/bin/java" -Djdk.lang.Process.launchMechanism=VFORK -cp "$B" SpawnProbe 2>&1 | head -3 | sed 's/^/ /'; done
|
||||||
|
echo; echo "=== the legacy ISO 639 language codes property (iw, ji, in)"
|
||||||
|
for v in 26 27; do jdk="JDK$v"; echo " JDK $v: java -Djava.locale.useOldISOCodes=true Compat"
|
||||||
|
"${!jdk}/bin/java" -Djava.locale.useOldISOCodes=true -cp "$B" Compat 2>&1 | sed 's/^/ /'; done
|
||||||
|
} > "$OUT/61-behaviour-changes.txt" 2>&1
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# New public API in 27, each exercised once (found by diffing javap dumps, see 51-api-diff-26-to-27.txt)"
|
||||||
|
"$JDK27/bin/java" --enable-preview -Dstdout.encoding=UTF-8 -cp "$B" Api27 2>&1
|
||||||
|
} > "$OUT/62-new-api.txt" 2>&1
|
||||||
|
cat "$OUT"/60-removed-options.txt "$OUT"/61-behaviour-changes.txt "$OUT"/62-new-api.txt
|
||||||
Executable
+101
@@ -0,0 +1,101 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# The Java 26 recap lane: what 26 changed that a 25 -> 27 upgrade meets in one go.
|
||||||
|
# ./scripts/recap26.sh -> docs/output/70-final-field-mutation.txt, 71-aot-cache-matrix.txt, 72-aot-startup.txt,
|
||||||
|
# 73-removed-in-26.txt, 74-api26.txt, 75-http3-fallback.txt
|
||||||
|
# Needs JDK 25 (LTS), 26 and 27 on disk.
|
||||||
|
set -uo pipefail
|
||||||
|
source "$(dirname "$0")/env.sh"
|
||||||
|
SRC="$ROOT/recap26/src"; B="$ROOT/build/recap26"; rm -rf "$B"; mkdir -p "$B/26" "$B/25" "$B/aot"
|
||||||
|
|
||||||
|
"$JDK26/bin/javac" -d "$B/26" "$SRC/Check.java" "$SRC/FinalFieldMutation.java" "$SRC/Api26.java" "$SRC/Http3Fallback.java"
|
||||||
|
"$JDK25/bin/javac" --release 25 -d "$B/25" "$SRC/FinalFieldMutation.java"
|
||||||
|
|
||||||
|
# ---------- JEP 500: final means final ----------
|
||||||
|
{
|
||||||
|
echo "# JEP 500: reflection writing to a final field, same class file behaviour on 25 and on 26"
|
||||||
|
echo; echo "=== JDK 25 (class compiled --release 25)"; "$JDK25/bin/java" -cp "$B/25" FinalFieldMutation 2>&1 | sed 's/^/ /'
|
||||||
|
echo; echo "=== JDK 26, defaults"; "$JDK26/bin/java" -cp "$B/26" FinalFieldMutation 2>&1 | sed 's/^/ /'
|
||||||
|
echo; echo "=== JDK 26, --enable-final-field-mutation=ALL-UNNAMED (the opt-in the warning asks for)"
|
||||||
|
"$JDK26/bin/java" --enable-final-field-mutation=ALL-UNNAMED -cp "$B/26" FinalFieldMutation 2>&1 | sed 's/^/ /'
|
||||||
|
echo; echo "=== JDK 26, --illegal-final-field-mutation=deny (what a future default will look like)"
|
||||||
|
"$JDK26/bin/java" --illegal-final-field-mutation=deny -cp "$B/26" FinalFieldMutation 2>&1 | sed 's/^/ /'
|
||||||
|
echo; echo "=== JDK 26, --illegal-final-field-mutation=debug (warning plus the offending stack trace)"
|
||||||
|
"$JDK26/bin/java" --illegal-final-field-mutation=debug -cp "$B/26" FinalFieldMutation 2>&1 | sed 's/^/ /' | head -12
|
||||||
|
for mode in "" "--enable-final-field-mutation=ALL-UNNAMED" "--illegal-final-field-mutation=deny"; do
|
||||||
|
echo; echo "=== JDK 26, JFR default settings, [${mode:-no flag}]: jfr print --events jdk.FinalFieldMutation"
|
||||||
|
rm -f "$B/ffm.jfr"
|
||||||
|
"$JDK26/bin/java" $mode -XX:StartFlightRecording:filename="$B/ffm.jfr",settings=default -cp "$B/26" FinalFieldMutation >/dev/null 2>&1
|
||||||
|
"$JDK26/bin/jfr" print --events jdk.FinalFieldMutation "$B/ffm.jfr" 2>&1 \
|
||||||
|
| grep -E 'jdk.FinalFieldMutation|declaringClass|fieldName|FinalFieldMutation.main' | sed 's/^ */ /' | sed -E 's/\{$//'
|
||||||
|
[ "${PIPESTATUS[0]}" -eq 0 ] || true
|
||||||
|
echo " events recorded: $("$JDK26/bin/jfr" summary "$B/ffm.jfr" | awk '/jdk.FinalFieldMutation /{print $2}')"
|
||||||
|
done
|
||||||
|
} > "$OUT/70-final-field-mutation.txt" 2>&1
|
||||||
|
|
||||||
|
# ---------- JEP 516: AOT object caching with any GC ----------
|
||||||
|
APP="$B/aot/AotApp"; mkdir -p "$APP"
|
||||||
|
cp "$ROOT/recap26/src/AotApp.java" "$APP/"
|
||||||
|
for v in 25 26 27; do jdk="JDK$v"; mkdir -p "$B/aot/$v/cls"
|
||||||
|
"${!jdk}/bin/javac" --release 25 -d "$B/aot/$v/cls" "$APP/AotApp.java"; (cd "$B/aot/$v/cls" && "${!jdk}/bin/jar" cf ../app.jar AotApp.class); done
|
||||||
|
mkcache() { # <jdk> <version> <gc> -> $B/aot/<version>/<gc>.aot
|
||||||
|
local j="$1" v="$2" gc="$3" d="$B/aot/$2"; rm -f "$d/$gc.aot" "$d/$gc.conf"
|
||||||
|
"$j/bin/java" -XX:+Use${gc}GC -XX:AOTMode=record -XX:AOTConfiguration="$d/$gc.conf" -cp "$d/app.jar" AotApp >/dev/null 2>&1
|
||||||
|
"$j/bin/java" -XX:+Use${gc}GC -XX:AOTMode=create -XX:AOTConfiguration="$d/$gc.conf" -XX:AOTCache="$d/$gc.aot" -cp "$d/app.jar" >/dev/null 2>&1; }
|
||||||
|
{
|
||||||
|
echo "# JEP 516: can the cache's archived heap objects be used? One row per (JDK, GC the cache was built under, GC at run time)."
|
||||||
|
echo "# 'heap objects' = the log says the archived module graph was used; 'unusable' = the JVM refused the whole cache."
|
||||||
|
for v in 25 26 27; do jdk="JDK$v"; for build in G1 Z; do mkcache "${!jdk}" "$v" "$build"
|
||||||
|
for run in Serial G1 Z; do
|
||||||
|
log="$("${!jdk}/bin/java" -XX:+Use${run}GC -XX:AOTCache="$B/aot/$v/$build.aot" -Xlog:aot=info -cp "$B/aot/$v/app.jar" AotApp 2>&1)"
|
||||||
|
if grep -q 'Unable to use AOT cache' <<<"$log"; then r="unusable: $(grep -o 'saved state of.*CDS will be disabled' <<<"$log" | head -1 | sed 's/, CDS will be disabled//')"
|
||||||
|
elif grep -q 'full module graph: enabled' <<<"$log"; then r="heap objects used (full module graph: enabled)"
|
||||||
|
else r="cache used, heap objects NOT used (full module graph: disabled)"; fi
|
||||||
|
printf 'JDK %s cache built under %-2s run under %-6s -> %s\n' "$v" "$build" "$run" "$r"
|
||||||
|
done; done; done
|
||||||
|
} > "$OUT/71-aot-cache-matrix.txt" 2>&1
|
||||||
|
|
||||||
|
median() { sort -n | awk '{a[NR]=$1} END{print a[int((NR+1)/2)]}'; }
|
||||||
|
bench() { # <jdk> <version> <gc> [cache-file]
|
||||||
|
local j="$1" v="$2" gc="$3" cache="${4:-}" i t0 t1; local -a ts=()
|
||||||
|
for i in 1 2 3 4 5 6 7 8 9; do
|
||||||
|
t0=$(date +%s%N)
|
||||||
|
if [ -n "$cache" ]; then "$j/bin/java" -XX:+Use${gc}GC -XX:AOTCache="$cache" -cp "$B/aot/$v/app.jar" AotApp >/dev/null 2>&1
|
||||||
|
else "$j/bin/java" -XX:+Use${gc}GC -Xshare:auto -XX:-AOTClassLinking -cp "$B/aot/$v/app.jar" AotApp >/dev/null 2>&1; fi
|
||||||
|
t1=$(date +%s%N); ts+=($(( (t1 - t0) / 1000000 )))
|
||||||
|
done; printf '%s\n' "${ts[@]}" | median; }
|
||||||
|
{
|
||||||
|
echo "# Wall-clock ms for 'java -cp app.jar AotApp' (median of 9), no AOT cache vs cache built under the same GC. INDICATIVE: shared 2-CPU sandbox."
|
||||||
|
for v in 25 26 27; do jdk="JDK$v"; for gc in G1 Z; do mkcache "${!jdk}" "$v" "$gc"
|
||||||
|
printf 'JDK %s %-2s no cache: %4s ms with cache: %4s ms\n' "$v" "$gc" "$(bench "${!jdk}" "$v" "$gc")" "$(bench "${!jdk}" "$v" "$gc" "$B/aot/$v/$gc.aot")"
|
||||||
|
done; done
|
||||||
|
} > "$OUT/72-aot-startup.txt" 2>&1
|
||||||
|
|
||||||
|
# committed transcripts must not carry the author's paths or per-run identity hashes
|
||||||
|
scrub() { sed -i -E "s#$ROOT#<repo>#g; s#@[0-9a-f]{6,8}#@<hash>#g" "$@"; }
|
||||||
|
scrub "$OUT/70-final-field-mutation.txt" "$OUT/71-aot-cache-matrix.txt" "$OUT/72-aot-startup.txt"
|
||||||
|
|
||||||
|
# ---------- JEP 504 and friends: things that were removed ----------
|
||||||
|
compile() { local jdk="$1" rel="$2" f="$3"; local out rc; out="$("$jdk/bin/javac" --release "$rel" -d "$B/rm" "$f" 2>&1)"; rc=$?
|
||||||
|
printf ' javac (JDK %s, --release %s): exit %s\n' "$4" "$rel" "$rc"; printf '%s\n' "$out" | grep -v '^Note:' | sed 's/^/ /'; }
|
||||||
|
mkdir -p "$B/rm"
|
||||||
|
{
|
||||||
|
echo "# Source that compiled on 25 and does not on 26"
|
||||||
|
for f in AppletGone ThreadStopGone; do echo; echo "=== recap26/src/broken/$f.java"
|
||||||
|
compile "$JDK25" 25 "$SRC/broken/$f.java" 25; compile "$JDK26" 26 "$SRC/broken/$f.java" 26; done
|
||||||
|
} > "$OUT/73-removed-in-26.txt" 2>&1
|
||||||
|
|
||||||
|
# ---------- new API in 26 ----------
|
||||||
|
{ "$JDK26/bin/java" -Dstdout.encoding=UTF-8 -cp "$B/26" Api26; echo "exit code: $?"; } > "$OUT/74-api26.txt" 2>&1
|
||||||
|
|
||||||
|
# ---------- JEP 517: HTTP/3 client, checkable half ----------
|
||||||
|
"$JDK26/bin/keytool" -genkeypair -alias demo -keyalg EC -groupname secp256r1 -dname "CN=localhost" -validity 2 -storetype PKCS12 \
|
||||||
|
-keystore "$B/ks.p12" -storepass changeit -ext san=dns:localhost >/dev/null 2>&1
|
||||||
|
{
|
||||||
|
echo "# JEP 517: a client that prefers HTTP/3 against a server that only speaks HTTP/1.1 over TLS (loopback, JDK's own HttpsServer)"
|
||||||
|
echo "# There is no live HTTP/3 server in this transcript: the sandbox has no UDP egress. The API surface is javap output below."
|
||||||
|
"$JDK26/bin/java" -cp "$B/26" Http3Fallback "$B/ks.p12" changeit 2>&1
|
||||||
|
echo; echo "# javap --module java.net.http -public java.net.http.HttpOption 'java.net.http.HttpOption\$Http3DiscoveryMode'"
|
||||||
|
"$JDK26/bin/javap" --module java.net.http -public 'java.net.http.HttpOption' 'java.net.http.HttpOption$Http3DiscoveryMode' 2>&1
|
||||||
|
echo; echo "# javap ... HttpClient\$Version"; "$JDK26/bin/javap" --module java.net.http -public 'java.net.http.HttpClient$Version' 2>&1 | grep -E 'HTTP_'
|
||||||
|
} > "$OUT/75-http3-fallback.txt" 2>&1
|
||||||
|
scrub "$OUT/73-removed-in-26.txt" "$OUT/74-api26.txt" "$OUT/75-http3-fallback.txt"
|
||||||
Executable
+20
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Regenerates every file in docs/output/. Needs: JDK 25 (LTS), JDK 26 and JDK 27 on disk, Docker with the
|
||||||
|
# amazoncorretto:26 and :27 images, and python3. Takes roughly 15-20 minutes, most of it the G1 cost runs.
|
||||||
|
#
|
||||||
|
# Non-deterministic outputs (timings, RSS, GC counts, the machine's preferred SIMD species) will differ on your
|
||||||
|
# machine; the shape should not. Everything else is byte-stable between runs.
|
||||||
|
set -uo pipefail
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
./g1-default.sh
|
||||||
|
./g1-cost.sh
|
||||||
|
BATCHES=400 OUTNAME=05-g1-cost-1cpu-short.txt ./g1-cost.sh
|
||||||
|
./g1-tuning.sh
|
||||||
|
./object-headers.sh
|
||||||
|
./jep-tour.sh
|
||||||
|
./broken-on-27.sh
|
||||||
|
./tls-pq.sh
|
||||||
|
./jfr-redaction.sh
|
||||||
|
./other-changes.sh
|
||||||
|
./recap26.sh
|
||||||
|
./api-diff.sh
|
||||||
Executable
+54
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# JEP 527: post-quantum hybrid key exchange in the JDK's default TLS 1.3 stack.
|
||||||
|
# ./scripts/tls-pq.sh -> docs/output/30-tls-groups-matrix.txt, 31-tls-clienthello-size.txt, 32-tls-opt-out.txt
|
||||||
|
set -euo pipefail
|
||||||
|
source "$(dirname "$0")/env.sh"
|
||||||
|
B="$ROOT/build/tls"; rm -rf "$B"; mkdir -p "$B"
|
||||||
|
"$JDK27/bin/javac" --release 26 -d "$B" "$ROOT"/jep-tour/src/TlsPeer.java "$ROOT"/jep-tour/src/ClientHelloSize.java
|
||||||
|
|
||||||
|
# A throwaway self-signed certificate for the loopback server; the same store is the client's trust store.
|
||||||
|
"$JDK27/bin/keytool" -genkeypair -alias demo -keyalg EC -groupname secp256r1 -dname "CN=localhost" -validity 2 \
|
||||||
|
-storetype PKCS12 -keystore "$B/ks.p12" -storepass changeit -ext san=dns:localhost >/dev/null 2>&1
|
||||||
|
STORE=(-Djavax.net.ssl.keyStore="$B/ks.p12" -Djavax.net.ssl.keyStorePassword=changeit
|
||||||
|
-Djavax.net.ssl.trustStore="$B/ks.p12" -Djavax.net.ssl.trustStorePassword=changeit)
|
||||||
|
DEBUG=(-Djavax.net.debug=ssl:handshake)
|
||||||
|
|
||||||
|
# handshake <server-jdk> <client-jdk> [extra client flag]
|
||||||
|
handshake() {
|
||||||
|
local sj="$1" cj="$2" cflag="${3:-}"; local pf="$B/port"; rm -f "$pf"
|
||||||
|
"$sj/bin/java" "${STORE[@]}" "${DEBUG[@]}" -cp "$B" TlsPeer server "$pf" > "$B/server.log" 2>&1 &
|
||||||
|
local spid=$!
|
||||||
|
for _ in $(seq 1 50); do [ -s "$pf" ] && break; sleep 0.1; done
|
||||||
|
"$cj/bin/java" $cflag "${STORE[@]}" "${DEBUG[@]}" -cp "$B" TlsPeer client "$(cat "$pf")" > "$B/client.log" 2>&1
|
||||||
|
wait "$spid"
|
||||||
|
echo "client offered : $(grep -m1 '"named groups"' "$B/client.log" | sed 's/^ *//')"
|
||||||
|
echo "server selected: $(awk '/"ServerHello"/{f=1} f&&/"named group"/{print $NF; exit}' "$B/client.log")"
|
||||||
|
grep -m1 'handshake complete' "$B/client.log"
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# Who talks to whom: the key-exchange group each pairing actually negotiates (TLS 1.3, loopback)"
|
||||||
|
for pair in "26 26" "27 27" "27 26" "26 27"; do
|
||||||
|
set -- $pair
|
||||||
|
cj="JDK$1"; sj="JDK$2"
|
||||||
|
echo; echo "=== client JDK $1 -> server JDK $2"
|
||||||
|
handshake "${!sj}" "${!cj}"
|
||||||
|
done
|
||||||
|
} > "$OUT/30-tls-groups-matrix.txt" 2>&1
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# Size of the first TLS record (the ClientHello) each JDK sends"
|
||||||
|
for v in 26 27; do
|
||||||
|
jdk="JDK$v"; echo; echo "=== JDK $v"
|
||||||
|
"${!jdk}/bin/java" -cp "$B" ClientHelloSize
|
||||||
|
done
|
||||||
|
echo; echo "=== JDK 27 with -Djdk.tls.namedGroups=x25519,secp256r1 (post-quantum group removed)"
|
||||||
|
"$JDK27/bin/java" -Djdk.tls.namedGroups=x25519,secp256r1 -cp "$B" ClientHelloSize
|
||||||
|
} > "$OUT/31-tls-clienthello-size.txt" 2>&1
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# Opting out on JDK 27: a client that does not offer the hybrid group"
|
||||||
|
echo; echo "=== client JDK 27 with -Djdk.tls.namedGroups=x25519,secp256r1 -> server JDK 27"
|
||||||
|
handshake "$JDK27" "$JDK27" "-Djdk.tls.namedGroups=x25519,secp256r1"
|
||||||
|
} > "$OUT/32-tls-opt-out.txt" 2>&1
|
||||||
|
cat "$OUT/30-tls-groups-matrix.txt" "$OUT/31-tls-clienthello-size.txt" "$OUT/32-tls-opt-out.txt"
|
||||||
Reference in New Issue
Block a user