Java堆外内存探索
引言
“墙内的人想尽办法要出去,墙外的人拼了命想进去”
作为一位Java的使用者,从一开始,JVM就帮我们处理好内存的分配回收问题,很多时候,我们也不用考虑这些问题。但是,随着越来越深入地使用Java,我们愈发觉得如果能够帮JVM减少一些重担,能够手动对内存进行控制,那么该多好!
YGC上的探索
记录的是掘金上的一篇文章https://juejin.im/post/5a9cb49df265da239706561a?utm_source=gold_browser_extension
- 主要讲解了YGC的过程包括old-gen-scanning(用于在YGC判断除了GC ROOTS外的OLD GEN对YOUNG的引用关系)
- 减低GC压力
- 把内存放到堆外(压力只存在于对堆外引用的扫描)
- 通信连接可以用堆外内存来分配,可以提高连接数同时降低JVM压力
- 避免破坏GC的分代假设,特别避免老年代引用新生代对象
讲讲如何使用堆外内存
Java中使用堆外内存有两种途径,即UnSafe和NIO的ByteBuffer,两者皆可,其实ByteBuffer的底层也是基于UnSafe去实现对堆外内存的使用。-XX:MaxDirectMemorySize=40M
- 区别
- UnSafe用完需要调用freeMemory(address)释放堆外内存
- ByteBuffer是基于JVM的堆内内存的DirecrByteBuffer引用,该引用被回收,则堆外内存就会回收
UnSafe
既然Unsafe类是堆外内存的使用始祖,那么,讲使用我们就直接翻过高墙,进去看看!!!
先说说UnSafe类的描述与功能:
- UnSafe类官方建议只在JDK内部使用,这样更满足JAVA的设计初衷,即那堵内存使用的高墙。
- 功能
- CAS(DouLea大师就是利用UnSafe实现CAS乐观锁)
- 使用堆外内存存储数据
- 阻塞唤醒线程
- 修改某个对象的属性的值
- 实例化对象
Sample
//以下代码用反射获取UnSafe实例
private static Unsafe unsafe = null;
static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
try {
unsafe = (Unsafe) field.get(null);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
//申请1KB内存
//保留address用于释放内存
long address = unsafe.allocateMemory(1024 * 1024);
//重新申请2KB内存
address=unsafe.reallocateMemory(address,1024*1024*2);
//存储
unsafe.putObject(String.class, 1024, "String");
//读取
String obj = (String) unsafe.getObject(String.class, 1024);
//CAS
boolean res = unsafe.compareAndSwapObject(String.class, 1024, "123", "124");
//..还有更多的JNI方法,有兴趣自己查看即可,例如可以获取某个对象某个属性的地址,然后进行操作等。
应用场景
- GC对性能的影响较大的时候,适合使用堆外内存降低GC压力从而提高性能
- 减少COPY——Netty的零拷贝技术
- 如果数据的发送需要从堆内存到堆外内存再到网络或者其它地方,则适合使用堆外内存减少数据的拷贝消耗
- 应用层缓存功能