sun.misc.Unsafe类允许您执行许多Java中不应该做的事情,但是在非常特殊的情况下仍然有用。 必须在99%的时间避免这种情况,但是在极少数情况下,这是唯一有意义的解决方案。
这篇文章考虑了它在OpenHFT中的使用方式以及我希望在Java 9中看到的功能。特别地,可以通过这种方式完成访问大量内存而不影响GC的工作。 只有在Java中才能通过这种方式在进程之间共享内存,而不会产生大量开销。
分配和释放堆内存。
public native long allocateMemory();
public native void freeMemory(long address);
这两种方法允许您分配任何大小的堆外内存。 它不限于Integer.MAX_VALUE字节,您会获得原始内存,可以在其中应用边界检查。 例如,Bytes.writeUTF(String)计算编码字符串的长度,并检查整个字符串是否适合一次(而不是每个字节)。
Java-Lang使用与DirectByteBuffer用来确保释放内存相同的内部Cleaner类。 理想情况下,这不会那么内部。
原始访问内存
public native Xxx getXxx(Object, long offset); // intrinsic
public native void putXxx(Object, long offset);// intrinsic
在这两种情况下,处理堆外内存时,Object均为null,并且偏移量只是地址。 这使您可以使用针对JVM的单个机器代码指令来执行RAW内存访问,这些指令会威胁到它们作为内在函数。 这显着提高了内存访问的性能。
这种原始方法的问题在于,您必须自己管理数据结构中字段的布局。 Java-Lang库通过允许您定义getter和setter的接口(甚至对于String和enums等对象类型)来解决此问题,它将在运行时生成实现。 即,您可以访问getter / setter,而无需知道“对象”已脱离堆。
线程安全访问内存
public native Xxx getVolatileXxx(Object, long offset); // intrinsic
public native void putOrderedXxx(Object, long offset); // intrinsic
这两种方法使您可以将惰性字段与惰性集一起使用。 对于设置线程,惰性设置速度更快,但是如果设置得太快,则可能导致同一线程读取旧值。 解决方案是不要读取您刚刚编写的值。
当在进程之间共享数据时,这些方法特别有用。
CAS操作
public native boolean compareAndSwapXxxx(Object, long offset, Xxx expected, Xxx setTo) // intrinsic
此方法对于构建锁定堆至关重要。 特别是在进程之间以线程安全的方式共享数据的最有效方法。 在我对Haswell i7-4500处理器进行的测试中,同一台计算机上两个进程的往返延迟通常是这样。
TCP协议 | – 9微秒。 |
文件锁 | – 5.5微秒。 |
中国科学院 | – 0.12微秒。 |
有序写 | – 0.02微秒(半往返,如果可以使用此模式) |
堆对象分配
public native Object allocateInstance(Class clazz);
在反序列化一个类时,您希望按照序列化时的方式重新构造该类中的值。 如JEP 187:序列化2.0所述,这不适用于当前的构造函数,一种解决方法是完全避免构造函数,并创建实例而不调用实例。 这在很大程度上取决于信任您拥有的数据,但是它具有易于使用的优点,并且不假设您拥有哪些构造函数。
结论
经常注意到,在没有网络开销的情况下,嵌入式数据库可以在延迟方面胜过分布式数据库。 我相信下一代低延迟数据库将提供嵌入式性能并在进程之间共享,并且更新和查询响应时间都将大大低于一微秒。
我认为没有理由不应该在Java中实现这些功能。 对于Java用户,本机接口性能最佳,因为它不需要JNI或将世界的C视图转换为Java视图。
翻译自: https://www.javacodegeeks.com/2014/01/sun-misc-unsafe-and-off-heap-memory.html