Java 25中Lambda内存开销测量及分配机制疑问
在Java 25 JShell中使用Java Object Layout(JOL)库测量,无JVM配置时Predicate<String>无状态Lambda的内存开销为16字节;开启Compact Object Headers(COH)后则为8字节。针对你提出的疑问,解答如下:
疑问1:无状态Lambda的16/8字节内存是静态提升仅分配一次,还是每次代码执行到Lambda时都分配?
无状态Lambda会被HotSpot JVM做静态提升优化,整个程序生命周期内只会分配一次实例,不会每次执行到Lambda代码时重新分配。无论代码中多少次触发该Lambda的创建逻辑,实际都复用同一个对象实例,内存开销仅计算一次的16/8字节。疑问2:如下生成百万个无状态Lambda的列表能否验证该问题?
对应的代码:List<Predicate<String>> list = IntStream.range(0, 1000000) .<Predicate<String>>mapToObj(each -> (x) -> x.isEmpty()) .toList()可以验证。你可以通过打印元素的
System.identityHashCode()或者用JOL分析对象引用,会发现列表中所有元素的哈希值完全一致,说明它们指向的是同一个Lambda实例。这直接证明无状态Lambda仅被分配一次,列表存储的都是同一对象的引用,而非百万个独立实例。疑问3:该行为是否会因JVM实现不同而变化?
会有差异。静态提升是HotSpot JVM(Oracle/OpenJDK默认实现)的优化策略,但其他JVM实现(如IBM J9、GraalVM Native Image等)可能采用不同的Lambda处理逻辑。部分JVM可能不对所有无状态Lambda做静态提升,或有专属的对象复用规则,具体行为需参考对应JVM的官方文档与实现细节。疑问4:若Lambda捕获局部变量或字段(如下示例),内存开销会如何变化?
对应的代码:List<Predicate<String>> list = IntStream.range(0, 1000000) .<Predicate<String>>mapToObj( each -> (x) -> x.isEmpty() || each % 2 == 0) .toList()这类捕获外部局部变量(此处为
each)的Lambda属于有状态Lambda,JVM无法进行静态提升,每次创建都会生成新实例。每个实例的内存开销除了基础对象头(16/8字节,取决于COH是否开启),还需加上捕获变量占用的内存。例如each是int类型,对象中会占用4字节(内存对齐后通常为8字节),因此每个实例总开销约为24字节(无COH:16+8)或16字节(开启COH:8+8)。百万个此类实例会占用对应倍数的内存,与无状态Lambda的内存占用差异极大。
内容的提问来源于stack exchange,提问作者Donald Raab

