Java堆外内存你分的清楚吗?不信来试试

编程入门 行业动态 更新时间:2024-10-26 02:36:02

Java堆外内存你分的清楚吗?<a href=https://www.elefans.com/category/jswz/34/1735089.html style=不信来试试"/>

Java堆外内存你分的清楚吗?不信来试试

Java的内存管理一直是一个很火的话题,今天聊一聊平常比较少关注的堆外内存,也叫直接内存,

不懂不影响生活,懂了就很高级,有木有!!!

看下图:

1、堆外内存是个啥🙄

堆外内存也叫直接内存,因为这部分内存就是机器的物理内存,够直接吧

直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中定义的内存区域。

使用native 函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer 对象作为这块内存的引用进行操作。

这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

举个通俗一点的例子🥭:

假如操作系统就是你所在小区,

小区的居民就是不同的jvm或者其他的进程(程序)。

有天你想装修,装修材料放在家里,也就是自己的空间,随便玩,放哪里你自己管理就好了,对应到虚拟机就是你常规理解的内存,包括heap,栈等等

但是把装修材料放到家里不方便,而且还要运进来,所以你想我能不能放在公共区域,比如在楼下找块空地放装修材料,这部分就是你要申请使用的堆外内存,也就是机器的物理内存,需要使用native 申请空间。

2.堆外内存的优缺点

优点🍉:

  1 减少了垃圾回收的工作,理论上能减小GC暂停时间,因为堆外内存的释放不受虚拟机管理(虚拟机只是释放句柄,而真正的内存是操作系统释放)

  2 省去了不必要的内存复制,实现zero copy,数据不需要再native memory和jvm memory中来回copy。

缺点🌶️:

  1 堆外内存难以控制,在发生内存泄漏的时候不易排查。谨慎使用。

  2 堆外内存相对来说,不适合存储很复杂的对象。一般则是放大块的内存。格式需要自己定义。

3、堆外内存的控制参数

可以通过设置-XX:MaxDirectMemorySize=10M控制堆外内存的大小。超过此内存则会报错。

堆外内存可以通过java.nio的ByteBuffer来创建,调用allocateDirect方法申请即可,

这是合法的途径

4、几个常用的类

4.1 unsafe

Unsafe类操作堆外内存

sun.misc.Unsafe提供了一组方法来进行堆外内存的分配,重新分配,以及释放。

  1. public native long allocateMemory(long size); —— 分配一块内存空间。
  2. public native long reallocateMemory(long address, long size); —— 重新分配一块内存,把数据从address指向的缓存中拷贝到新的内存块。
  3. public native void freeMemory(long address); —— 释放内存。
public class UnsafeTest {public static void main(String[] args) throws Exception {//最简单的使用方式是基于反射获取Unsafe实例Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);Unsafe unsafe = (Unsafe) f.get(null);long address = unsafe.allocateMemory(1024);unsafe.freeMemory(address);
}
}

注意:直接使用unsafe 会报错,因为Unsafe不让使用,会产生安全问题

4.2 DirectByteBuffer

ByteBuffer.allocateDirect(1024);

JVM在堆内只保存堆外内存的引用,用DirectByteBuffer对象来表示,类似指针的概念

每个DirectByteBuffer对象在初始化时,都会创建一个对应的Cleaner对象。

这个Cleaner对象会在合适的时候执行unsafe.freeMemory(address),从而回收这块堆外内存。一般在full gc的时候回收

当DirectByteBuffer对象在某次YGC中被回收,只有Cleaner对象知道堆外内存的地址。

当下一次FGC执行时,Cleaner对象会将自身Cleaner链表上删除,并触发clean方法清理堆外内存。

此时,堆外内存将被回收,Cleaner对象也将在下次YGC时被回收。

如果JVM一直没有执行FGC的话,无法触发Cleaner对象执行clean方法,从而堆外内存也一直得不到释放。

5、内存泄漏的分析

当堆外内存占用较多时,可以尝试强制执行Full Gc看堆外内存是否有下降,这样会调用cleaner 释放堆外内存,如果有下降就很有可能时DirectByteBuffer的原因了。

最好是在启动参数上增加-XX:MaxDirectMemorySize=x[m|g],

例如-XX:MaxDirectMemorySize=500m

最大分配的堆外内存500M,超过这个范围就是OOM。在不设置的时候默认是机器的内存

sun.misc.VM.java里面是如何设置默认的directmemorysize的

public static long maxDirectMemory() {  if (booted)  return directMemory;  Properties p = System.getProperties();  String s = (String)p.remove("sun.nio.MaxDirectMemorySize");  System.setProperties(p);  if (s != null) {  if (s.equals("-1")) {  // -XX:MaxDirectMemorySize not given, take default// 翻译以下 如果不设置  XX:MaxDirectMemorySize 取默认值就是最大内存directMemory = Runtime.getRuntime().maxMemory();  } else {  long l = Long.parseLong(s);  if (l > -1)  directMemory = l;  }  }  return directMemory;  
}  

6、应用场景

使用堆外内存有两大场景

6.1 和nio有关的一些API需要使用堆外内存

操作系统内核直接把数据写到堆外内存里,不需要像普通API一样,操作系统内核缓存一份,程序读的时候再复制一份到程序空间,减少了复制的过程,提高了性能,Netty 就是这样做的。

6.2 为业专门调优的时候

根据业务管理堆外开辟一块内存,然后自己管理。

比如一个连接来了批量申请1M给1000个小对象用,连接断开了直接批量释放这1M内存,(如果对于GC那可是管理1000个对象的负担,GC不知道这1000个对象用完可以直接批量释放)因为写代码的人最懂内存里发生了什么事情,所以可以自己写代码用最高效的办法去使用内存。

7、其他知识

使用堆外内存的两种途径,一种是上面的方式

还有一种是JNI写java的c/c++扩展,jni 自己管理内存,和jvm 不关联,只要使用就行

总结:

堆外内存是直接对物理内存的使用,这部分的操作可以直接理解为指针的操作,使用堆外内存扩展了jvm的边界,

缺点就是离开了jvm的管理,对于对内存管理不是很熟悉的同学可能会有风险,

这是一把双刃剑,用的好则无敌,用不好则万劫不复,

在使用之前一定要了解,不然只会埋下深坑。

码字不容易,希望获得大家的三连支持

更多推荐

Java堆外内存你分的清楚吗?不信来试试

本文发布于:2024-03-13 23:06:12,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1735092.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:不信   内存   Java

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!