Java中另类使用内存的方法

本文深入探讨了 Java 中 sun.misc.Unsafe 类的各种高级用法,包括内存管理、原始内存访问、线程安全操作以及 CAS 操作等。Unsafe 类允许开发者绕过 Java 的安全限制,直接操作内存,对于低延迟应用开发具有重要意义。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

sun.misc.Unsafe为你大开Java的方便之门,你可以用它做很多Java不允许的事情,在一些非常特殊的场景下它还是非常有用的。99%的时候,你都应该避免使用它,然而在有些非常罕见的情况下,只有它能解决问题。
本文讲述了它在OpenHFT中的使用场景以及我希望在Java 9中看到哪些功能。如果希望访问大量内存的同时又不影响GC,就特别适合使用Unsafe。在进程间共享内存,同时又不希望引起显著的开销,在Java中就只有这么一种方法了。

[b]分配及释放堆内存
[/b]
public native long allocateMemory();
public native void freeMemory(long address);


你可以用这两个方法分配任何大小的堆内存。它不受Integer.MAX_VALUE字节的大小限制,返回给你的是原始内存,需要的话你可以进行边界检查。比如,Bytes.writeUTF(String)会计算编码的字符串的长度,检查是否容纳的下整个字符串,当然只做一次校验,不会每个字节都检查一遍。
java.lang里面也有一个类似的Cleaner类,DirectByteBuffer就用它来确保内存已经被释放了。不过这个类不太应该放到这么核心的地方。

[b]访问原始内存[/b]
public native Xxx getXxx(Object, long offset); // intrinsic
public native void putXxx(Object, long offset);// intrinsic

在这两组方法中,当访问的是堆外的内存时,Object是为空的,offset就是实际的地址。在把它们当作内部函数的JVM上,你可以只用一条机器指令就可以访问原始内存。这极大的提高了内存访问的效率。
这种访问方式的问题就是,你得自己去维护你数据结构里面各个字段的分布。java.lang库里的解决办法是,它让你定义getter和setter方法,然后它在运行时生成具体的实现。也就是说,不管对象是堆内还是堆外的,你就直接通过getter和setter来访问它们就好了。

[b]线程安全地访问内存[/b]
public native Xxx getVolatileXxx(Object, long offset); // intrinsic
public native void putOrderedXxx(Object, long offset); // intrinsic

有了这两组方法,你可以通过一个懒加载的集合来把一个字段模拟成volatile类型的。线程通过这个集合来设置值速度会更快,不过如果这个线程很快又读取值的话可能会读到旧的值。解决方法就是不要去读刚写完的值。
在进程中共享内存时,这两组方法尤其管用。

[b]CAS操作[/b]
public native boolean compareAndSwapXxxx(Object, long offset, Xxx expected, Xxx setTo)

想创建一个堆外的锁,这组方法是少不了的。在进程间安全的共享数据的话,这也是最高效的一种方式。从我在Haswell处理器i7-4500上做的测试来看,同一机器上的两个进程间的通讯往返延迟的表现相当不错;
<table summary="日志级别">
<tbody>
<tr>
<td>TCP</td>
<td>- 9 micro-seconds.</td>
</tr>
<tr>
<td>FileLocks</td>
<td>- 5.5 micro-seconds.</td>
</tr>
<tr>
<td>CAS</td>
<td>- 0.12 micro-seconds.</td>
</tr>
<tr>
<td>Ordered write</td>
<td>- 0.02 micro-seconds.</td>
</tr>
</tbody>
</table>
[b]堆对象的分配[/b]
public native Object allocateInstance(Class clazz);

当类反序列时,你当然希望类里面的值会恢复成序列前的样子。不过在现在的构造方法中,这个可能会有点小问题,[url=http://openjdk.java.net/jeps/187]JEP 187: Serialization 2.0[/url]中有提到过这个问题。一个解决方法就是彻底不使用构造方法来创建新对象。这说明你得充分信任你的数据正确性,好处就是它易于使用且不用关心到底用哪个构造方法来实例化对象。

[b]结论[/b]
嵌入式的数据库由于没有额外的网络开销,在请求延迟方面是远优于分布式数据库的。我相信下一代低延迟的数据库不但能达到嵌入式数据库的性能,还能在进程间进行共享,同时它的更新和查询的响应时间都能在毫秒级以下。

Java没理由会不去实现它。对Java用户而言,本地接口的性能是最佳的,因为它不需要JNI,或者说是不需要进行Java和C之间的一层转化。

译者注:文章假定读者对Unsafe有一定的了解和认识,更详细的说明请关注[url=http://it.deepinmind.com]deepinmind[/url]或本博客的后续文章
文章同时发表于本人博客:[url=http://it.deepinmind.com/java/2014/03/09/Java%E4%B8%AD%E5%8F%A6%E7%B1%BB%E4%BD%BF%E7%94%A8%E5%86%85%E5%AD%98%E7%9A%84%E6%96%B9%E6%B3%95.html]deepinmind[/url]

更多文章请关注:[url=http://it.deepinmind.com]Java译站[/url]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值