从Java 6开始,要求标准化非堆存储(off-heap)作为Java内部API的提议就已经在JDK强化提案(JEP)中被提出。这种方式的处理能力和堆存储(on-heap)一样高效,并且没有堆存储使用中的一些局限问题。堆存储在百万数量级瞬时使用的对象/值下工作的相当好,但是一旦你试图存储十亿数量级的对象/值时,你就要想办法去避免垃圾回收带来的持续增加的延迟。并且有时系统会要求同时保证大量数据处理和低延迟。非堆存储就是有这样一种能力:独立管理内存空间而不产生垃圾回收压力。Java中管理集合的两个类”Queue“和"HashMap"使用起来相当方便,如果使用这两个已有接口再加上我们自己的垃圾回收机制实现起来应该不是很难。这样既能实现大量数据存储并且能大大减少延迟,相比而言,原有的堆存储方式很容易产生内存不足错误,随之就要重启服务了。
这篇文章将会研究 JEP所带来的影响,将使得我们获悉类似于Java HashMap和新的off-heap的性能。简言之,JEP可能就有“指导”HashMap这个可爱的老家伙的一些新特性的魔法。 JEP所述的特性,在OpenJDK的发布来看,相对于传统的Java平台优先级做了许多重大的改变。
1、关于安全性的重构,这一sun.misc.Unsafe上的有用的部分,被放入了 新的API包。
2、提倡使用新的API包,直接影响高性能的本地内存操作(在off-heap上的本地内存操作对象上)。
3、(通过新的API)提供一个 外部函数接口(FFI)桥 针对Java直接操作系统资源和系统调用。
4、许可了Java运行时能辅助 硬件事务性内存(Hardware Transactional Memory)的提供者能把焦点集中在重写低并发字节码到高并发的 speculatively branched机器码。
5、移除了FUD(坦率的讲这是一种技术偏见),这与使用off-heap编程策略来提升Java的执行性能有关。总的来讲,JEP有几点是很清楚的,在OpenJDK平台上,相对于曾经的 dark craft, secret society of off-heap practitioners,现在的主流对开放是拥抱的。
本文力求(用普遍而温和的方式)让所有对此感兴趣的 Java 开发者都能有所收获。作者希望即使新手也能跟上本文节奏,而不会有看不懂的“磕磕绊绊”;因此不要气馁,耐心坐下来读完吧。本文努力介绍一些历史背景,为以下问题提供思路:
堆存储 HashMap 的问题是怎么产生的?
在解决这个问题上面,有过哪些经验/教训?
在堆存储 HashMap 的应用情景中,有哪些仍未解决的问题?
新的 JEP 提供的功能(将 HashMap 非堆存储)能带来哪些好处?
未来的 JEP 在解决现在尚未解决的问题上面,有哪些值得期待之处?
那就让我们一起开始这段旅程吧。值得记住的是,在 Java 出现之前,hash 表是实现在原生内存堆中的,如C 和 C++ 都是如此。某种意义上来说,重新引入非堆存储是重新介绍一些古老的技巧,这些技巧当代的开发者往往不曾了解。各种意义上来说,这都是一次“回到未来”的旅程。旅途愉快!
OpenJDK的非堆存储(Off-Heap)的强化提案(JEP)
已经有一些非堆存储(Off-Heap)的强化提案(JEP)被提出来。下面描绘了一个提供非堆存储(Off-Heap)内存的最低要求。方案试图替代现在sun.misc.Unsafe所提供的内容,不仅如此,这些方案还提供了另外一些有用的功能。
提案总结:总的来说就是为sun.misc.Unsafe创建了一个替代的部分,这样就可以不用直接使用那个库。
直接目标:移除需要直接访问的内部类。
间接目标:不提供那些不推荐的方法,也不实现那些不安全(Unsafe)的方法。
成功标准:提供一种方式去实现那些重要的功能,并且达到与那些不安全(Unsafe)和 FileDispatcherImpl的方式一样的性能。
提案动机:当前不安全(Unsafe)的方式就意味着就需要构建更大的,线程上更安全的非堆存储(Off-Heap)结构。这对于最小化垃圾处理器(GC)的开销有益。这对于在进程和内嵌数据库之间的内存共享可以不用C语言和JNI,这也就有可能提供更快更多的移动计算性能。当前的FileDispatcherImpl方式用于实现任意大小内存的映射。(标准API被限制在2GB以内。)
描述:为非堆存储(off-heap)提供一个包装类(类似于 ByteBuffer) ,还需要下面的增强。
64位的大小和偏移量
对于易失(volatile)的和有序的访问以及比较和交换的操作上有线程安全的结构。
JVM优化边界检查,开发者控制边界检查。(允许提供安全性设置)
有能力在同一缓冲区的不同记录复用一份缓冲。
有能力去映射一个非堆存储(off-heap)数据结构,让缓冲区在优化过的方式下进行边界检查。
保留关键功能
支持内存映射文件
支持NIO
支持把写操作提交到磁盘
候选方案:直接使用sun.misc.Unsafe
测试:sun.misc.Unsafe和内存映射文件有同样的测试需求。附加的测试应该工作在同样的方式下,要求展示的线程安全的操作为AtomicXxxx类。AtomicXxxx类应该被重写并且单独使用公共的API。
风险: 当一群开发者使用了Unsafe之后,他们可能一致认为没有更适合的替代品。这意味着JEP的范围很广,或者创建了新的JEP覆盖了Unsafe中的其他功能。
其他JDK : NIO
兼容性: 提供了向后兼容的库。它兼容java7,如果你有足够的兴趣去研究的话,也有可能兼容java6。(截止到这篇文章,Java 7是当前的版本)。
安全性: 在理想情况下,安全的风险性不能超过ByteBuffer太多。
性能和可扩展性: 优化边界检查是困难的。为了添加更多的普通操作,则需要把功能添加到新的缓冲区,以减少开销,例如读写UTF。