系统级语言 · AOT · LLVM · 自举
一门单线程的提前编译语言,属于 Java 家族 — 但不是 Java 的子集。没有 VM,没有 JIT,没有垃圾回收器。熟悉的类、泛型与异常;底层是原生速度与 arena 作用域内存。
// 看起来像 Java — 运行时没有 GC。
public class Report {
public static void main(String[] args) {
int total = 0;
arena scratch { // 作用域内存
int[] rows = new int[1<<16];
for (int i = 0; i < rows.length; i++)
total += (rows[i] = i * 3);
} // 在这里释放 — O(1),无停顿
System.out.println(total);
}
}
核心主张
SuperJ 编译为 LLVM IR 再生成机器码。没有字节码,没有类加载,没有追踪式回收器,也没有线程 — 整个程序在编译期就完全确定,所以行为可预测到每一次分配。
一个全局 arena 给整个程序用,再加上词法 arena {} 块 — 退出时一次 O(1) 释放,没有 stop-the-world 停顿。
直接生成优化后的机器码。没有 VM,没有 JIT 预热,没有要打包的运行时。
线程、锁、管程和内存模型都消失了 — 从结构上就是确定性的。
SuperJ 编译器用 SuperJ 编写 — 工具链端到端自举。我们用自己的语言打造自己的工具链。
招牌特性
在 arena 块里分配短生命周期对象;块退出时整个 arena 一次性释放。如果一个引用的生命周期会超出它所在的 arena,那就是一个编译错误,而不是潜伏的 use-after-free。
Node createNode(int v) {
return new Node(v); // 全局 arena — 可以返回
}
void process() {
arena temp {
Buffer buf = new Buffer(1024); // 在 temp 中
Node n = createNode(5); // 在全局 arena
// outer = buf; ← 被拒绝:逃逸出它的 arena
} // temp 释放:buf 被回收,n 仍然有效
}
词法的,不是动态的。 一个 new 属于它书写所在的那个 arena — 被调用的方法仍然使用全局 arena,除非它们自己开了 arena。
↳ n(全局 arena)在块退出后仍然存活。
系统能力
在 arena 模型之外,SuperJ 还提供了那些你原本得退回 C 才能写出来的原语 — 同时类型系统依然在背后替你把关。
arena {} 实现 O(1) 批量回收;local 实现栈作用域引用。
一等公民 i128、i256、i512 原语,为加密与大数运算而生。
无头的值类型,有线格式上确定的布局 — 协议与 mmap 的理想选择。
int[,] — 连续存储、按步长索引,和锯齿数组 int[][] 是不同的类型。
native 方法直接调用 C ABI — 没有 JNI,没有编组层。
Class::method 指向一个单抽象方法接口 — 无捕获,没有闭包。
类级与方法级,带边界参数,以及 ? extends / ? super 通配符。
可移植的向量族(Byte64、Float8、…),lowering 为原生 SIMD IR。
一个局部变量在赋值前被读取就是编译错误 — 不会意外读到垃圾值。
取舍
每一次移除都换来可预测性;每一项保留都是当初让 Java 高效的部分。
synchronized、volatilefinalize()String / string,统一的不可变类型证明
一个跑在内置 HTTP 协议栈上的 Web 服务器,编译成单一原生二进制,在回环节点上加压测试 — 服务器钉在一个核上,压力发生器钉在另一个核上。它的响应速度比 wrk 发请求的速度还快。
测量环境:Ryzen 9 9950X3D,服务器与客户端各占一核(服务器在 CPU 4,客户端在 CPU 3):SuperJ 自己的流水线客户端能达到 ~515K req/s,而 wrk 顶到 ~423K — 瓶颈是压力发生器,不是服务器。在持续压力下服务器的尾部保持平坦 — p50 156µs、p99 167µs — 因为没有回收器会停顿。查看完整基准测试 →