get(index) / set(index, value) のように、配列っぽく安全に読み書きできるString や List<T> のような参照型はまだ非対応で、対象はかなり「低レベル寄り」この記事で紹介されている TypedMemory は、ひと言でいうと、
Javaのrecordを、強い型を保ったまま off-heap memory にマッピングするライブラリ
です。
これ、地味に見えてかなり面白いです。
普通のJavaプログラミングって、new でオブジェクトを作って、ガベージコレクションに任せる世界ですよね。
でもこのライブラリが狙っているのは、そういう「Javaらしい安心感」ではなくて、C/C++っぽいメモリ操作の世界に、Javaの型安全さを持ち込むことだと思います。
しかも、ただのラッパーではありません。
「この record はこういうメモリ配置になる」といったことを、FFM API を通してかなり明示的に扱えるのがポイントです。
一般のJava開発では、オブジェクトは通常 heap(ヒープ)に置かれます。
ヒープはJavaの管理下にあるメモリで、GC(ガベージコレクション)が面倒を見てくれます。
一方で off-heap memory は、その外側のメモリです。
ざっくり言うと、
という特徴があります。
ただし、便利なぶん難しいです。
手で管理する要素が増えるので、offset 計算やlayout 定義を間違えるとすぐ事故ります。
ここがまさに TypedMemory の出番で、低レベルの強さは残しつつ、手間を減らす方向に振っています。
README にある特徴を、噛み砕くとこんな感じです。
Javaの record は、データを入れるための小さな型を作るのに便利です。
TypedMemory では、その record を メモリの構造そのものとして扱えるのが魅力です。
たとえば Point(float x, float y) のような record を定義して、その配列のようなものを off-heap memory 上に作れます。
通常、低レベルメモリを扱うときは、
みたいなことを一つずつ考える必要があります。
TypedMemory は、record の component から MemoryLayout を推論してくれるので、こうした面倒をかなり肩代わりしてくれます。
これはかなりありがたいです。正直、こういう世界で一番つらいのは「バグっても見た目でわかりにくいこと」なので、型とレイアウトの橋渡しをしてくれるのは価値が高いと思います。
Mem.of(Point.class, arena, 10) のように作って、get(0) / set(0, value) で読み書きします。
感覚としては、普通の配列やリストに近いです。
ただし裏では heap ではなく off-heap に置かれているので、速度・配置・寿命管理の観点はかなり違います。
README の例では、こんな感じで使っています。
record Point(float x, float y) {}
try (Arena arena = Arena.ofConfined()) {
Mem<Point> points = Mem.of(Point.class, arena, 10);
points.set(0, new Point(5, 3));
Point point = points.get(0);
IO.println(point);
}
やっていることはシンプルです。
Arena でメモリの置き場を確保するMem<Point> として、Point 型の連続したメモリ領域を作るset で書くget で読むここで大事なのは、arena が寿命管理を担当することです。
これによって、「いつ解放するか」をある程度わかりやすく管理できます。
個人的には、ここがFFM API系の面白さの核だと思っています。
Javaなのに、メモリ管理の感覚はかなりシステム寄り。なのに、完全な手作業ではない。
この中間地点をうまく狙っているのが TypedMemory です。
TypedMemory は、record の component をもとにメモリ配置を作ります。
対応している形はかなり絞られています。
boolean, byte, short, char, int, long, float, double@size(n) を付けた配列@size(4) float[] weightsrecord Pixel(short x, short y) {}
record Particle(
int id,
float x,
float y,
Pixel origin,
@size(4) float[] weights,
@size(3) Pixel[] trail
) {}
この設計は、かなり「メモリレイアウト重視」です。
逆にいうと、何でも自由に扱えるわけではありません。
README では、以下は今のところ非対応とされています。
String のようなオブジェクト参照List<T> などのコレクションint[][] のような多次元配列つまり、これは汎用のデータクラス保存庫ではありません。
むしろ、構造が固定されたデータを、効率よく並べるための仕組みです。
ここは誤解しやすいところなので要注意です。
Javaの普通のオブジェクトモデルをそのまま拡張するものではなく、かなり低レベルな世界に踏み込んだライブラリだと思っておくのがよさそうです。
README では、次の用途が挙げられています。
これ、見ているだけでも「速度が命」の匂いがします。
たとえば、ゲームやシミュレーションでは、似た形のデータを大量に並べて扱うことが多いですよね。
そのとき、オブジェクトをバラバラに持つより、連続したメモリにまとめたほうがキャッシュ効率がよいことがあります。
もちろん、ここはケースバイケースです。
「何でもかんでも速くなる」わけではないですが、データの並び方を意識したい世界ではかなり魅力があると思います。
README では、TypedMemory は experimental と明記されています。
つまり、
ということです。
ここは大事です。
面白いライブラリではあるものの、本番導入は慎重にという空気があります。
新しいAPIを触る楽しさはある一方で、長期安定を求める現場では様子見になるのではないでしょうか。
このライブラリは Java 25 以上が必要です。
README では、理由として ClassFile API を挙げています。
また、reinterpret のような機能を使う場合は、native access のフラグも必要です。
たとえば jar 実行ならこんな感じです。
java --enable-native-access=ALL-UNNAMED -jar app.jar
モジュール化しているなら、さらに指定が必要です。
java --enable-native-access=your.module.name -m your.module.name/com.example.Main
このへんは、いかにもJavaの「安全のために少し儀式が多い」感じが出ています。
面倒ではあるけれど、そのぶん危険な操作を明示的に許可する設計なので、私はわりと好感があります。
README を通して感じる TypedMemory の個性を一言でいうなら、
低レベルなことをしているのに、気分はかなりJavaらしい
です。
普通、メモリ操作といえば「怖い」「バグりやすい」「読むのがつらい」という印象が強いですが、TypedMemory はそこに record と generics 的な読みやすさを持ち込んでいます。
この発想はかなり好きです。
ただし、逆にいうと「Javaの便利さ」を期待しすぎるとギャップがあります。
あくまで対象は、
という場面です。
個人的に面白いと思ったのは、TypedMemory が 「隠す」のではなく「整理する」方向に寄っていることです。
最近の高級なライブラリは、内部の複雑さをなるべく隠して、使う側には「ただの簡単なAPI」に見せることが多いです。
それはそれで便利ですが、TypedMemory は少し違います。
メモリや layout の存在をちゃんと残したまま、でも扱いやすくしている。
このバランスはかなり好みが分かれそうですが、私はかなり面白い設計だと思いました。
特に、FFM API に沿ったまま、record ベースで使いやすくしているのが良いです。
Javaでシステム寄りの開発をしたい人にとって、こういう中間層はたしかに欲しくなるはずです。
TypedMemory は、Java 25 の FFM API を使って、record を strongly typed な off-heap memory にマッピングするライブラリです。
「Javaでここまでメモリを直接扱うのか」と驚く人もいそうですが、むしろその挑戦がこのライブラリの一番の魅力だと思います。
シンプルなWebアプリには不要でも、性能やレイアウトが重要な世界ではかなり光る存在ではないでしょうか。