http://www.tianshouzhi.com/api/tutorials/netty/331

我们已经知道,在网络编程中,为了避免频繁的在用户空间与内核空间拷贝数据,通常会直接从内核空间中申请内存,存放数据,在Java中,把内核空间的内存称之为直接内存,nio包中的ByteBufferallocateDirect方法,就是帮助我们申请直接内存的,代码如下所示:

  1. public static ByteBuffer allocateDirect(int capacity) {
  2. return new DirectByteBuffer(capacity);
  3. }

在上述代码片段中,返回的是一个DirectByteBuffer对象,其是ByteBuffer的子类,对于直接内存的分配,就是在这个类中实现的。

有经验的读者可能知道,在java中,直接内存的申请与释放是通过Unsafe类的allocateMemory方法和freeMemory方法来实现的,且对于直接内存的释放,必须手工调用freeMemory方法,因为JVM只能帮我们管理堆内存,直接内存不在其管理范围之内。

DirectByteBuffer帮我们简化了直接内存的使用,我们不需要直接操作Unsafe类来进行直接内存的申请与释放,那么其是如何实现的呢?

直接内存的申请:

在DirectByteBuffer实例通过构造方法创建的时候,会通过Unsafe类的allocateMemory方法 帮我们申请直接内存资源。

直接内存的释放:

DirectByteBuffer本身是一个Java对象,其是位于堆内存中的,JDK的GC机制可以自动帮我们回收,但是其申请的直接内存,不再GC范围之内,无法自动回收。好在JDK提供了一种机制,可以为堆内存对象注册一个钩子函数(其实就是实现Runnable接口的子类),当堆内存对象被GC回收的时候,会回调run方法,我们可以在这个方法中执行释放DirectByteBuffer引用的直接内存,即在run方法中调用Unsafe 的freeMemory 方法。注册是通过sun.misc.Cleaner类来实现的。

下面通过源码进行分析:

  1. class DirectByteBuffer extends MappedByteBuffer  implements DirectBuffer
  2. {
  3. ....
  4. //构造方法
  5. DirectByteBuffer(int cap) {                   // package-private
  6. super(-1, 0, cap, cap);
  7. boolean pa = VM.isDirectMemoryPageAligned();
  8. int ps = Bits.pageSize();
  9. long size = Math.max(1L, (long)cap + (pa ? ps : 0));//对申请的直接内存大小,进行重新计算
  10. Bits.reserveMemory(size, cap);
  11. long base = 0;
  12. try {
  13. base = unsafe.allocateMemory(size); //分配直接内存,base表示的是直接内存的开始地址
  14. } catch (OutOfMemoryError x) {
  15. Bits.unreserveMemory(size, cap);
  16. throw x;
  17. }
  18. unsafe.setMemory(base, size, (byte) 0);
  19. if (pa && (base % ps != 0)) {
  20. // Round up to page boundary
  21. address = base + ps - (base & (ps - 1));
  22. } else {
  23. address = base;
  24. }
  25. cleaner = Cleaner.create(this, new Deallocator(base, size, cap));//注册钩子函数,释放直接内存
  26. att = null;
  27. }
  28. ....
  29. }

可以看到构造方法中的确是用了unsafe.allocateMemory方法帮我们分配了直接内存,另外,在构造方法的最后,通过 Cleaner.create方法注册了一个钩子函数,用于清除直接内存的引用。

Cleaner.create方法声明如下所示:

  1. public static Cleaner create(Object heapObj, Runnable task)

其中第一个参数是一个堆内存对象,第二个参数是一个Runnable任务,表示这个堆内存对象被回收的时候,需要执行的回调方法。我们可以看到在DirectByteBuffer的最后一行中,传入的这两个参数分别是this,和一个Deallocator(实现了Runnable接口),其中this表示就是当前DirectByteBuffer实例,也就是当前DirectByteBuffer被回收的时候,回调Deallocator的run方法

Deallocator就是用于清除DirectByteBuffer引用的直接内存,代码如下所示:

  1. private static class Deallocator
  2. implements Runnable
  3. {
  4. private static Unsafe unsafe = Unsafe.getUnsafe();
  5. private long address;
  6. private long size;
  7. private int capacity;
  8. private Deallocator(long address, long size, int capacity) {
  9. assert (address != 0);
  10. this.address = address;
  11. this.size = size;
  12. this.capacity = capacity;
  13. }
  14. public void run() {
  15. if (address == 0) {
  16. // Paranoia
  17. return;
  18. }
  19. unsafe.freeMemory(address);//清除直接内存
  20. address = 0;
  21. Bits.unreserveMemory(size, capacity);
  22. }
  23. }

可以看到run方法中调用了unsafe.freeMemory方法释放了直接内存的引用。

关于System.gc对直接内存释放的影响

细心的读者,可能注意到了,在DirectByteBuffer实例创建的时候,分配内存之前调用了Bits.reserveMemory方法,如果分配失败调用了Bits.unreserveMemory,同时在Deallocator释放完直接内存的时候,也调用了Bits.unreserveMemory方法。

这两个方法,主要是记录jdk已经使用的直接内存的数量,当分配直接内存时,需要进行增加,当释放时,需要减少,源码如下:

  1. static void reserveMemory(long size, int cap) {
  2. //如果直接有足够多的直接内存可以用,直接增加直接内存引用的计数
  3. synchronized (Bits.class) {
  4. if (!memoryLimitSet && VM.isBooted()) {
  5. maxMemory = VM.maxDirectMemory();
  6. memoryLimitSet = true;
  7. }
  8. // -XX:MaxDirectMemorySize limits the total capacity rather than the
  9. // actual memory usage, which will differ when buffers are page
  10. // aligned.
  11. if (cap <= maxMemory - totalCapacity) {//维护已经使用的直接内存的数量
  12. reservedMemory += size;
  13. totalCapacity += cap;
  14. count++;
  15. return;
  16. }
  17. }
  18. //如果没有有足够多的直接内存可以用,先进行垃圾回收
  19. System.gc();
  20. try {
  21. Thread.sleep(100);//休眠100秒,等待垃圾回收完成
  22. } catch (InterruptedException x) {
  23. // Restore interrupt status
  24. Thread.currentThread().interrupt();
  25. }
  26. synchronized (Bits.class) {//休眠100毫秒后,增加直接内存引用的计数
  27. if (totalCapacity + cap > maxMemory)
  28. throw new OutOfMemoryError("Direct buffer memory");
  29. reservedMemory += size;
  30. totalCapacity += cap;
  31. count++;
  32. }
  33. }
  34. //释放内存时,减少引用直接内存的计数
  35. static synchronized void unreserveMemory(long size, int cap) {
  36. if (reservedMemory > 0) {
  37. reservedMemory -= size;
  38. totalCapacity -= cap;
  39. count--;
  40. assert (reservedMemory > -1);
  41. }
  42. }

通过上面代码的分析,我们事实上可以认为Bits类是直接内存的分配担保,当有足够的直接内存可以用时,增加直接内存应用计数,否则,调用System.gc,进行垃圾回收,需要注意的是,System.gc只会回收堆内存中的对象,但是我们前面已经讲过,DirectByteBuffer对象被回收时,那么其引用的直接内存也会被回收,试想现在刚好有其他的DirectByteBuffer可以被回收,那么其被回收的直接内存就可以用于本次DirectByteBuffer直接的内存的分配。

因此我们经常看到,有一些文章讲解在使用Nio的时候,不要禁用System.gc,也就是启动JVM的时候,不要传入-XX:+DisableExplicitGC参数,因为这样可能会造成直接内存溢出。道理很明显,因为直接内存的释放与获取比堆内存更加耗时,每次创建DirectByteBuffer实例分配直接内存的时候,都调用System.gc,可以让已经使用完的DirectByteBuffer得到及时的回收。

虽然System.gc只是建议JVM去垃圾回收,可能JVM并不会立即回收,但是频繁的建议,JVM总不会视而不见。

不过,这并不是绝对的,因为System.gc导致的是FullGC,可能会暂停用户线程,也就是JVM不能继续响应用户的请求,对于一些要求延时比较短的应用,是不希望JVM频繁的进行FullGC的。

所以笔者的建议是:禁用System.gc,调大最大可以使用的直接内存。如:

-XX:+DisableExplicitGC -XX:MaxDirectMemorySize=256M
 

【转载】 DirectByteBuffer内存释放的更多相关文章

  1. 谈谈Linux内存释放

    上上周吧,一个朋友问我说他公司的服务器内存free 为0 是为什么,意思大概是内存去哪了,这引发了一个小小的讨论,也就是内存释放的问题… 首先我们可能会用free 去查看内存的使用率,它应该是这样的 ...

  2. Netty源码—七、内存释放

    Netty本身在内存分配上支持堆内存和直接内存,我们一般选用直接内存,这也是默认的配置.所以要理解Netty内存的释放我们得先看下直接内存的释放. Java直接内存释放 我们先来看下直接内存是怎么使用 ...

  3. 关于vector的内存释放问题

    以前一直想当然的以为vector 的clear()函数会保证释放vector的内存,今天网上一查资料发现完全不是我想象的那样子. 比如有如下代码: tempObject obj1; tempObjec ...

  4. STL 内存释放

    C++ STL 中的map,vector等内存释放问题是一个很令开发者头痛的问题,关于 stl内部的内存是自己内部实现的allocator,关于其内部的内存管理本文不做介绍,只是 介绍一下STL内存释 ...

  5. java 笔记(1)-—— JVM基础,内存数据,内存释放,垃圾回收,即时编译技术JIT,高精度类型

    1.java中5个存放数据的地方: (1).寄存器(Registers):位于CPU内部,是速度最快的存储区,但是数量和容量有限.在java中不能直接操作寄存器. (2).栈(Stack):栈位于通用 ...

  6. zw版【转发·台湾nvp系列Delphi例程】.NET调用HALCON COM控件内存释放模式

    zw版[转发·台湾nvp系列Delphi例程].NET调用HALCON COM控件内存释放模式 ------------------------------------方法一 :Imports Sys ...

  7. WebBrowser的内存释放

    WebBrowser窗口自动滚动: this.webBrowser.Document.Window.ScrollTo(0, webBrowser1.Document.Body.ScrollRectan ...

  8. [super dealloc]内存释放的先后顺序

    心得:从前做内存释放,只是觉得应该,没体会到这个的重要性,如果不及时释放就会有很多内存泄露,就像我早期遇到的前赴后继的崩溃,比如:没使用完,就释放会崩溃等明显的release问题.     作为全局的 ...

  9. MKMapView的内存释放问题

    MKMapView的内存释放问题 by 伍雪颖 - (void)dealloc { self.mapView.showsUserLocation = NO; self.mapView.userTrac ...

  10. Unity3D内存释放

    Unity3D内存释放 最近网友通过网站搜索Unity3D在手机及其他平台下占用内存太大. 这里写下关于Unity3D对于内存的管理与优化. Unity3D 里有两种动态加载机制:一个是Resourc ...

随机推荐

  1. 墨天轮访谈 | IvorySQL王志斌—IvorySQL,一个基于PostgreSQL的兼容Oracle的开源数据库

    分享嘉宾:王志斌 瀚高IvorySQL产品经理 整理:墨天轮社区 导读 大家好,我是瀚高IvorySQL产品经理王志斌,IvorySQL是基于PostgreSQL的衍生开源项目. 我今天分享的内容主要 ...

  2. 妙用编辑器:使用Notepad--宏功能提高维护指令生成生成效率

    应用场景 日常维护工作中,需要快速生成一批指令来完成某些操作,比如:快速添加一批节点. 目标指令列表如下: ADD NODE: ID=1, NAME="NODE_1"; ADD N ...

  3. 云原生爱好者周刊:这款支持全平台的 Podman Desktop 值得一试

    开源项目推荐 Podman Desktop Companion Podman 桌面客户端,支持 macOS.Windows 和 Linux 平台,后端支持原生 Podman(仅支持 Linux).Po ...

  4. 第147篇:微信小程序开发中Promise的使用(aysnc,await)

    好家伙, 0.错误描述 今天在开发中犯了一个比较严重的错误 对于Promise的错误使用 场景: 微信小程序中展示搜索条件列表 // API请求工具函数 const apiRequest = (url ...

  5. 三大主流负载均衡软件对比(LVS+Nginx+HAproxy)

    LVS: 优点 : 1.抗负载能力强.性能高,能达到F5硬件的60%:对内存和cpu资源消耗比较低2.工作在网络4层,通过vrrp协议转发(仅作分发之用),具体的流量由linux内核处理,因此没有流量 ...

  6. att&ck学习笔记1

    一.环境搭建 1.1环境搭建测试 最近想要开始学习内网渗透,搜集了一些教程,准备先实验一个vulnstack靶机,熟悉一下内网渗透操作再学习基础知识. 靶场下载地址:http://vulnstack. ...

  7. Jenkins Job触发其他远程Job

    https://blog.csdn.net/diaojian66/article/details/117334537 如果不想遇到连接远程Jenkins主机失败后的反复尝试,去掉认证会是一个不错的选择 ...

  8. .NET Core 泛型底层原理浅谈

    简介 泛型参考资料烂大街,基本资料不再赘述,比如泛型接口/委托/方法的使用,逆变与协变. 泛型好处有如下几点 代码重用 算法重用,只需要预先定义好算法,排序,搜索,交换,比较等.任何类型都可以用同一套 ...

  9. Redis中常见的数据类型及其应用场景

    五种常见数据类型 Redis中的数据类型指的是 value存储的数据类型,key都是以String类型存储的,value根据场景需要,可以以String.List等类型进行存储. 各数据类型介绍: R ...

  10. 【Azure 环境】从网络包中分析出TLS加密套件信息

    问题描述 在抓取到网络包之后,如何来获取TLS信息呢?比如使用的是是么加密套件呢? 因为在应用层面,获取的错误信息非常简单: An TLS 1.2 connection request was rec ...