对无 GC(无堆)Java 的实验

主要内容总结:

  • 关于Volodymyr Gubarkov:2024 年 2 月,在https://github.com/xonixx/gc_less仓库中进行了无 GC(无堆)Java 的一系列实验,使用sun.misc.Unsafejava.lang.foreign.MemorySegment
  • 为何进行实验:出于好玩和好奇,始于想检查著名的sun.misc.Unsafe方法在最新 Java 中是否仍可行,还想尝试像在 C 中那样用 Java 编程。
  • 实验方法

    • sun.misc.Unsafe:该类在最新 Java 23 版本仍受支持,但很危险,容易使 JVM 崩溃,但其有与堆外内存管理相关的方法族。
    • 数据结构实现:作为无 GC 编程的实际应用,实现了数组、数组列表、栈、哈希表等基本数据结构,通过模板生成不同 Java 类型的实现,模板本身是可运行代码且对应long专门化版本,方便测试确保所有专门化实现正确,生成脚本为gen.awk
    • 启用 Epsilon GC:为确保不意外使用堆,启用了 Epsilon GC 设置,它不实现实际的内存回收机制,堆耗尽时 JVM 会关闭。
    • 一些实现细节:以类似 OOP 的“奇怪”方式实现数据结构,所有方法静态,接受long address作为参数,代替thistry-with-resources结合CleanerRef类负责释放内存,Ref类用于注册对象清理;实现了内存泄漏检测,通过在内存分配时记录位置,释放时丢弃来检测。
    • 可视化演示:手动内存管理离堆的好处之一是确定性内存使用,Main4类提供了循环中分配和释放的可视化演示。
    • java.lang.foreign.MemorySegment:最近出现 JEP“Deprecate Memory-Access Methods in sun.misc.Unsafe for Removal”,提供了更安全的替代方案java.lang.foreign.MemorySegment类,它是“管理”的原生内存 API,虽看起来更重,但实验表明基于MemorySegment的哈希表实现堆内存消耗为 O(1),这是因为MemorySegment是值类,在 JIT 编译时会被内联消除所有分配。还实现了类似 Python 的离堆哈希表实现,通过重新分配连续内存来管理数据增长。
    • 带有限堆的压力测试:创建实验程序,在固定 20MB 堆大小的 Epsilon-GC 下,比较Unsafe、Python 风格和MemorySegment三种哈希表实现,结果三种实现都通过测试。
    • 基准测试:创建AccessSpeedTest.java测量读写速度,int[]MemorySegment在速度上无明显差异;IntHashtableSpeedTest测试不同哈希表实现的速度,表明无 GC 算法通常比常规 GC 全算法慢,特定情况下map<int,int>的 Python 风格实现比内置HashMap快 8 倍。
  • 总结收获:在 Java 中可以使用手动内存管理,如需要确定性内存消耗;速度不是使用离堆算法的好理由,相同算法离堆时慢 3 倍以上,可能是 JIT 编译器对常规 Java 代码有更好的优化机会;建议在实现中优先使用java.lang.foreign.MemorySegment而不是sun.misc.Unsafe。如果发现错误或有其他反馈,可发邮件至 mailto:xonixx@gmail.com
阅读 16
0 条评论