【转载】 DirectByteBuffer内存释放
http://www.tianshouzhi.com/api/tutorials/netty/331
我们已经知道,在网络编程中,为了避免频繁的在用户空间与内核空间拷贝数据,通常会直接从内核空间中申请内存,存放数据,在Java中,把内核空间的内存称之为直接内存,nio包中的ByteBuffer
的allocateDirect
方法,就是帮助我们申请直接内存的,代码如下所示:
- public static ByteBuffer allocateDirect(int capacity) {
- return new DirectByteBuffer(capacity);
- }
在上述代码片段中,返回的是一个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
类来实现的。
下面通过源码进行分析:
- class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer
- {
- ....
- //构造方法
- DirectByteBuffer(int cap) { // package-private
- super(-1, 0, cap, cap);
- boolean pa = VM.isDirectMemoryPageAligned();
- int ps = Bits.pageSize();
- long size = Math.max(1L, (long)cap + (pa ? ps : 0));//对申请的直接内存大小,进行重新计算
- Bits.reserveMemory(size, cap);
- long base = 0;
- try {
- base = unsafe.allocateMemory(size); //分配直接内存,base表示的是直接内存的开始地址
- } catch (OutOfMemoryError x) {
- Bits.unreserveMemory(size, cap);
- throw x;
- }
- unsafe.setMemory(base, size, (byte) 0);
- if (pa && (base % ps != 0)) {
- // Round up to page boundary
- address = base + ps - (base & (ps - 1));
- } else {
- address = base;
- }
- cleaner = Cleaner.create(this, new Deallocator(base, size, cap));//注册钩子函数,释放直接内存
- att = null;
- }
- ....
- }
可以看到构造方法中的确是用了unsafe.allocateMemory
方法帮我们分配了直接内存,另外,在构造方法的最后,通过 Cleaner.create方法注册了一个钩子函数,用于清除直接内存的引用。
Cleaner.create方法声明如下所示:
- public static Cleaner create(Object heapObj, Runnable task)
其中第一个参数是一个堆内存对象,第二个参数是一个Runnable任务,表示这个堆内存对象被回收的时候,需要执行的回调方法。我们可以看到在DirectByteBuffer的最后一行中,传入的这两个参数分别是this
,和一个Deallocator
(实现了Runnable接口),其中this表示就是当前DirectByteBuffer实例,也就是当前DirectByteBuffer被回收的时候,回调Deallocator
的run方法
Deallocator就是用于清除DirectByteBuffer引用的直接内存,代码如下所示:
- private static class Deallocator
- implements Runnable
- {
- private static Unsafe unsafe = Unsafe.getUnsafe();
- private long address;
- private long size;
- private int capacity;
- private Deallocator(long address, long size, int capacity) {
- assert (address != 0);
- this.address = address;
- this.size = size;
- this.capacity = capacity;
- }
- public void run() {
- if (address == 0) {
- // Paranoia
- return;
- }
- unsafe.freeMemory(address);//清除直接内存
- address = 0;
- Bits.unreserveMemory(size, capacity);
- }
- }
可以看到run方法中调用了unsafe.freeMemory方法释放了直接内存的引用。
关于System.gc对直接内存释放的影响
细心的读者,可能注意到了,在DirectByteBuffer实例创建的时候,分配内存之前调用了Bits.reserveMemory
方法,如果分配失败调用了Bits.unreserveMemory
,同时在Deallocator释放完直接内存的时候,也调用了Bits.unreserveMemory
方法。
这两个方法,主要是记录jdk已经使用的直接内存的数量,当分配直接内存时,需要进行增加,当释放时,需要减少,源码如下:
- static void reserveMemory(long size, int cap) {
- //如果直接有足够多的直接内存可以用,直接增加直接内存引用的计数
- synchronized (Bits.class) {
- if (!memoryLimitSet && VM.isBooted()) {
- maxMemory = VM.maxDirectMemory();
- memoryLimitSet = true;
- }
- // -XX:MaxDirectMemorySize limits the total capacity rather than the
- // actual memory usage, which will differ when buffers are page
- // aligned.
- if (cap <= maxMemory - totalCapacity) {//维护已经使用的直接内存的数量
- reservedMemory += size;
- totalCapacity += cap;
- count++;
- return;
- }
- }
- //如果没有有足够多的直接内存可以用,先进行垃圾回收
- System.gc();
- try {
- Thread.sleep(100);//休眠100秒,等待垃圾回收完成
- } catch (InterruptedException x) {
- // Restore interrupt status
- Thread.currentThread().interrupt();
- }
- synchronized (Bits.class) {//休眠100毫秒后,增加直接内存引用的计数
- if (totalCapacity + cap > maxMemory)
- throw new OutOfMemoryError("Direct buffer memory");
- reservedMemory += size;
- totalCapacity += cap;
- count++;
- }
- }
- //释放内存时,减少引用直接内存的计数
- static synchronized void unreserveMemory(long size, int cap) {
- if (reservedMemory > 0) {
- reservedMemory -= size;
- totalCapacity -= cap;
- count--;
- assert (reservedMemory > -1);
- }
- }
通过上面代码的分析,我们事实上可以认为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内存释放的更多相关文章
- 谈谈Linux内存释放
上上周吧,一个朋友问我说他公司的服务器内存free 为0 是为什么,意思大概是内存去哪了,这引发了一个小小的讨论,也就是内存释放的问题… 首先我们可能会用free 去查看内存的使用率,它应该是这样的 ...
- Netty源码—七、内存释放
Netty本身在内存分配上支持堆内存和直接内存,我们一般选用直接内存,这也是默认的配置.所以要理解Netty内存的释放我们得先看下直接内存的释放. Java直接内存释放 我们先来看下直接内存是怎么使用 ...
- 关于vector的内存释放问题
以前一直想当然的以为vector 的clear()函数会保证释放vector的内存,今天网上一查资料发现完全不是我想象的那样子. 比如有如下代码: tempObject obj1; tempObjec ...
- STL 内存释放
C++ STL 中的map,vector等内存释放问题是一个很令开发者头痛的问题,关于 stl内部的内存是自己内部实现的allocator,关于其内部的内存管理本文不做介绍,只是 介绍一下STL内存释 ...
- java 笔记(1)-—— JVM基础,内存数据,内存释放,垃圾回收,即时编译技术JIT,高精度类型
1.java中5个存放数据的地方: (1).寄存器(Registers):位于CPU内部,是速度最快的存储区,但是数量和容量有限.在java中不能直接操作寄存器. (2).栈(Stack):栈位于通用 ...
- zw版【转发·台湾nvp系列Delphi例程】.NET调用HALCON COM控件内存释放模式
zw版[转发·台湾nvp系列Delphi例程].NET调用HALCON COM控件内存释放模式 ------------------------------------方法一 :Imports Sys ...
- WebBrowser的内存释放
WebBrowser窗口自动滚动: this.webBrowser.Document.Window.ScrollTo(0, webBrowser1.Document.Body.ScrollRectan ...
- [super dealloc]内存释放的先后顺序
心得:从前做内存释放,只是觉得应该,没体会到这个的重要性,如果不及时释放就会有很多内存泄露,就像我早期遇到的前赴后继的崩溃,比如:没使用完,就释放会崩溃等明显的release问题. 作为全局的 ...
- MKMapView的内存释放问题
MKMapView的内存释放问题 by 伍雪颖 - (void)dealloc { self.mapView.showsUserLocation = NO; self.mapView.userTrac ...
- Unity3D内存释放
Unity3D内存释放 最近网友通过网站搜索Unity3D在手机及其他平台下占用内存太大. 这里写下关于Unity3D对于内存的管理与优化. Unity3D 里有两种动态加载机制:一个是Resourc ...
随机推荐
- 013 Python 变量的内存管理(学点底层东西显得你异于常人)
#!/usr/bin/env python # -*- coding:utf-8 -*- # Datatime:2022/7/18 21:13 # Filename:011 Python约定俗称的常量 ...
- 安装完Oracle数据库后需要调整的参数
关闭审计 alter system set audit_trail = none scope=spfile; 180天密码过期 alter profile default limit PASSWORD ...
- 12万字的java面试题及答案整理(2024新版)
前言 本来想着给自己放松一下,刷刷博客,慕然回首,final有哪些用法?static都有哪些用法?java的精度算法?java运算逻辑?异常处理?似乎有点模糊了,那就大概看一下Java基础面试题吧.好记 ...
- 13-2 c++拷贝控制和资源管理
目录 13.2.1 行为像值的类 类拷贝赋值运算符的编写 13.2.2 定义行为像指针的类 引用计数 定义一个使用引用计数的类 为了定义这些成员,我们首先必须确定此类型对象的拷贝语义.一般来说,有两种 ...
- 选型4G-Cat.1模组Air780E,必须要说的注意事项!
Air780E是合宙低功耗4G-Cat.1模组经典型号之一,累计出货数量2000万+,广泛应用于物联网各行业.在此,特别感谢各位大佬的信任与支持. 写这篇文档的目的是什么呢? 从用户的角度,解答大 ...
- Abp Vnext 中如何统一接口返回值
ABP Vnext Vue 的实现 https://github.com/WangJunZzz/abp-vnext-pro 在使用 abp 的过程中,如果提供给第三方接口要实现返回值统一需要怎么做? ...
- vue2-vuex
专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 vue 应 用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信 应用场景: 多个组 ...
- MySQL-8.3.0 innovation 创新版本YUM安装配置
MySQL-8.3.0 innovation版本已发布了,想抢先体验一下最新的功能,可以用以下的方式快速在虚拟机上安装一下哈 服务器环境:[root@node213 ~]# cat /etc/redh ...
- nodejs版本管理工具之n
转载: https://juejin.cn/post/7065534944101007391 Node.js 对于现在的前端开发人员来说是不可或缺的需要掌握的技能,但我们在使用时避免不了会需要切换不同 ...
- highcharts中的环形图
环形图如下效果: 代码: that.options = { chart: { type: 'pie', backgroundColor: 'transparent', color: '#fff', / ...