Skip to content

Jdk23 #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .DS_Store
Binary file not shown.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ build
debug/
target/

# BC: in our gradle build we changed the target directory to be build/cargo
build/cargo

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
Expand Down
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
"type": "java",
"name": "GameClient (on ZGC)",
"request": "launch",
"vmArgs": "-XX:+UseZGC -XX:+ZGenerational",
"vmArgs": "-XX:+UseZGC",
"mainClass": "org.example.GameClient",
"projectName": "app",
"env": {
Expand All @@ -135,7 +135,7 @@
"type": "java",
"name": "GameServer (on ZGC)",
"request": "launch",
"vmArgs": "-XX:+UseZGC -XX:+ZGenerational",
"vmArgs": "-XX:+UseZGC",
"mainClass": "org.example.GameServer",
"projectName": "app",
"env": {
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ Pairs with the ongoing blog post: [Road to JDK 25 - Over-Engineering Tic-Tac-Toe

### Features

https://openjdk.org/projects/jdk/23/

- **JEP467**: Markdown Documentation Comments
- **JEP474**: ZGC: Generational Mode by Default
- **JEP471**: Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal

https://openjdk.org/projects/jdk/22/

- **JEP454**: Foreign Function & Memory API
Expand Down
Binary file modified app/.DS_Store
Binary file not shown.
26 changes: 20 additions & 6 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ repositories {
// https://doc.rust-lang.org/cargo/getting-started/installation.html
val osName = System.getProperty("os.name").lowercase()

val cargoBuildDir = file("${buildDir}/cargo")
val cargoBuildDir = file("${layout.buildDirectory.get()}/cargo")

val libPath = when {
osName.contains("win") -> "${cargoBuildDir}/debug"
Expand Down Expand Up @@ -88,6 +88,20 @@ dependencies {
runtimeOnly("ch.qos.logback:logback-classic:1.5.6")
runtimeOnly("org.slf4j:slf4j-api:2.0.13")
runtimeOnly("org.slf4j:slf4j-jdk-platform-logging:2.0.13")


// JDK23: JMH (Third-Party) Not required, added for benchmarking
// https://github.com/openjdk/jmh
implementation("org.openjdk.jmh:jmh-core:1.37")
annotationProcessor("org.openjdk.jmh:jmh-generator-annprocess:1.37")
}

// Run JMH benchmark
// ./gradlew jmh
tasks.register<JavaExec>("jmh") {
mainClass.set("org.openjdk.jmh.Main")
classpath = sourceSets["main"].runtimeClasspath
args = listOf("org.example.interop.benchmark.PlayerIdsBenchmark")
}

testing {
Expand All @@ -103,7 +117,7 @@ testing {
// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(22)
languageVersion = JavaLanguageVersion.of(23)
}
}

Expand All @@ -123,7 +137,7 @@ graalvmNative {
javaLauncher = javaToolchains.launcherFor {
// NB: On MacOS ARM ARCH the native-image implementation is not available
// for the versions of GRAAL_VM Community edition - selecting Oracle
languageVersion = JavaLanguageVersion.of(22)
languageVersion = JavaLanguageVersion.of(23)
vendor = JvmVendorSpec.matching("Oracle")
// languageVersion = JavaLanguageVersion.of(17)
// vendor = JvmVendorSpec.GRAAL_VM
Expand All @@ -141,7 +155,7 @@ application {
// WARNING: java.lang.foreign.SymbolLookup::libraryLookup has been called by org.example.GameBoardNativeImpl in an unnamed module
// WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
// WARNING: Restricted methods will be blocked in a future release unless native access is enabled
applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED")
applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "-XX:+UseZGC")
}

tasks.run.configure {
Expand All @@ -160,7 +174,7 @@ tasks.withType<Test>().all {
// WARNING: java.lang.foreign.SymbolLookup::libraryLookup has been called by org.example.GameBoardNativeImpl in an unnamed module
// WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
// WARNING: Restricted methods will be blocked in a future release unless native access is enabled
jvmArgs = listOf("--enable-native-access=ALL-UNNAMED")
jvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "-XX:+UseZGC")
environment("PATH", libPath) // For Windows
environment("LD_LIBRARY_PATH", libPath) // For Linux
environment("DYLD_LIBRARY_PATH", libPath) // For macOS
Expand All @@ -177,7 +191,7 @@ tasks.named<JavaExec>("run") {
// WARNING: java.lang.foreign.SymbolLookup::libraryLookup has been called by org.example.GameBoardNativeImpl in an unnamed module
// WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
// WARNING: Restricted methods will be blocked in a future release unless native access is enabled
jvmArgs = listOf("--enable-native-access=ALL-UNNAMED")
jvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "-XX:+UseZGC")
environment("PATH", libPath) // For Windows
environment("LD_LIBRARY_PATH", libPath) // For Linux
environment("DYLD_LIBRARY_PATH", libPath) // For macOS
Expand Down
17 changes: 10 additions & 7 deletions app/src/main/java/org/example/GameBoard.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,16 @@ public interface GameBoard extends JsonSerializable {
*/
int getDimension();

/**
* Converts the game board to a JSON string representation for serialization. Format corresponds
* to the following JSON schema with content as a 1D array of strings of size dimension x
* dimension: {@snippet : { "dimension": int, "content": [ string, ... ] } }
*
* @return the game board as a JSON string
*/
///
/// Converts the game board to a JSON string representation for serialization. Format
/// corresponds to the following JSON schema with content as a 1D array of strings of size
/// dimension x dimension.
///
/// ```javascript
/// { "dimension": int, "content": [ string, string, ..., string ] } }
/// ```
/// @return the game board as a JSON string
/// @see JsonSerializable
String asJsonString();

/**
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/org/example/JsonSerializable.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package org.example;

/// This interface defines a contract for classes that can be serialized to JSON strings.
/// Any class that implements this interface must provide an implementation of the `asJsonString()`
/// method, which returns the JSON representation of the object.
public interface JsonSerializable {

/// Returns the JSON representation of the object.
/// @return the JSON string representation of the object.
String asJsonString();
}
28 changes: 15 additions & 13 deletions app/src/main/java/org/example/Player.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package org.example;

/**
* Tic-tac-toe player interface for all players
* {@snippet :
* // Create a human player
* Player player = new HumanPlayer("X"); // @highlight region="player" substring="player"
*
* // Choose the next valid move on the game board
* int validBoardLocation = player.nextMove(gameBoard); // @end
* }
*/
/// Tic-tac-toe player interface for all players
/// {@snippet :
/// // Create a human player
/// Player player = new HumanPlayer("X"); // @highlight region="player" substring="player"
///
/// // Choose the next valid move on the game board
/// int validBoardLocation = player.nextMove(gameBoard); // @end
/// }
public sealed interface Player permits HumanPlayer, BotPlayer, RemoteBotPlayer {


/// Returns the marker (e.g. "X" or "O") used by this player.
/// @return the player's marker
String getPlayerMarker();

/// Chooses the next valid move on the game board.
/// @param board the current state of the game board
/// @return the index of the next valid move on the board
int nextMove(GameBoard board);

}
}
1 change: 0 additions & 1 deletion app/src/main/java/org/example/SecureMessageHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ private void registerSecurityProviders() {
* @throws InvalidParameterSpecException if the received Kyber parameters are invalid
* @throws InvalidAlgorithmParameterException if the Kyber parameters are invalid
* @throws InvalidKeyException if the public/private key is invalid
* @throws EncapsulateException if the encapsulation of the shared secret fails
* @throws DecapsulateException if the decapsulation of the shared secret fails
* @throws ClassNotFoundException
*/
Expand Down
31 changes: 31 additions & 0 deletions app/src/main/java/org/example/interop/PlayerIds.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.example.interop;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;

public class PlayerIds {

private volatile int nextId;

private static final VarHandle NEXT_ID_VH;

static {
try {
NEXT_ID_VH = MethodHandles.lookup().findVarHandle(PlayerIds.class, "nextId", int.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}

public PlayerIds(int initialValue) {
this.nextId = initialValue;
}

public int getNextId() {
return nextId;
}

public int getNextIdAndIncrement() {
return (int) NEXT_ID_VH.getAndAdd(this, 1);
}
}
13 changes: 7 additions & 6 deletions app/src/main/java/org/example/interop/TicTacToeGameBoard.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import java.lang.ref.Cleaner;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.example.GameBoard;
Expand All @@ -28,12 +27,14 @@ class TicTacToeGameBoard implements GameBoard {

private final SymbolLookup libTicTacToe;

private final AtomicInteger nextId;
private final PlayerIds playerIds;

private final Map<String, Integer> playerMarkerToId;
private final Map<Integer, String> idToPlayerMarker;

private final Cleaner cleaner;

@SuppressWarnings("unused")
private final Cleaner.Cleanable cleanable;

private MethodHandle newGameBoard;
Expand All @@ -48,7 +49,7 @@ public TicTacToeGameBoard(int dimension, SymbolLookup libTicTacToe, Cleaner clea
this.libTicTacToe = libTicTacToe;
this.playerMarkerToId = new HashMap<>();
this.idToPlayerMarker = new HashMap<>();
this.nextId = new AtomicInteger(1);
this.playerIds = new PlayerIds(1);
this.initGameBoardMethods();
this.board = newGameBoard(dimension);
this.cleaner = cleaner;
Expand All @@ -73,7 +74,7 @@ public TicTacToeGameBoard(int dimension, SymbolLookup libTicTacToe, Cleaner clea
this.libTicTacToe = libTicTacToe;
this.playerMarkerToId = new HashMap<>(playerMarkerToId);
this.idToPlayerMarker = new HashMap<>(idToPlayerMarker);
this.nextId = new AtomicInteger(initialValue);
this.playerIds = new PlayerIds(initialValue);
this.initGameBoardMethods();
this.board = board;
this.cleaner = cleaner;
Expand Down Expand Up @@ -109,7 +110,7 @@ public boolean hasMovesAvailable() {
@Override
public GameBoard withMove(String playerMarker, int location) {
if (!playerMarkerToId.containsKey(playerMarker)) {
int id = nextId.getAndIncrement();
int id = playerIds.getNextIdAndIncrement();
playerMarkerToId.put(playerMarker, id);
idToPlayerMarker.put(id, playerMarker);
}
Expand All @@ -121,7 +122,7 @@ public GameBoard withMove(String playerMarker, int location) {
newBoard,
playerMarkerToId,
idToPlayerMarker,
nextId.get(),
playerIds.getNextId(),
libTicTacToe,
cleaner);
} catch (Throwable e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package org.example.interop.benchmark;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import org.example.interop.PlayerIds;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;

@SuppressWarnings("unused")
public class PlayerIdsBenchmark {

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void testPlayerIdsGetId() {
PlayerIds ids = new PlayerIds(1);
for (int i = 0; i < 1000; i++) {
var id = ids.getNextId();
}
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void testPlayerIdsGetAndIncrementId() {
PlayerIds ids = new PlayerIds(1);
for (int i = 0; i < 1000; i++) {
var id = ids.getNextIdAndIncrement();
}
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void testAtomicIntegerGetId() {
AtomicInteger ids = new AtomicInteger(1);
for (int i = 0; i < 1000; i++) {
var id = ids.get();
}
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void testAtomicIntegerGetAndIncrement() {
AtomicInteger ids = new AtomicInteger(1);
for (int i = 0; i < 1000; i++) {
var id = ids.getAndIncrement();
}
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void testLockGetId() {
Control ids = new Control(1);
for (int i = 0; i < 1000; i++) {
var id = ids.getId();
}
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void testLockGetAndIncrement() {
Control ids = new Control(1);
for (int i = 0; i < 1000; i++) {
var id = ids.getAndIncrement();
}
}

/// Naive implementation of an id generator.
private static class Control {
private final ReentrantLock lock = new ReentrantLock();

private int id = 1;

private Control(int initialValue) {
this.id = initialValue;
}

private int getId() {
lock.lock();
try {
return id;
} finally {
lock.unlock();
}
}

private int getAndIncrement() {
lock.lock();
try {
int oldId = id;
id = id + 1;
return oldId;
} finally {
lock.unlock();
}
}
}
}
Loading
Loading