Java的堆外内存本来是高贵而神秘的东西,只在一些缓存方案的收费企业版里出现。但自从用了Netty,就变成了天天打交道的事情,毕竟堆外内存能减少IO时的内存复制,不需要堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中;而且也没了烦人的GC。
好在,Netty所用的堆外内存只是Java NIO的 DirectByteBuffer类,通读一次很快。还有一些sun.misc.*的类木有源码,要自己跑去OpenJdk那看个明白。
1. 堆外内存的创建
在DirectByteBuffer中,首先向Bits类申请额度,Bits类有一个全局的 totalCapacity变量,记录着全部DirectByteBuffer的总大小,每次申请,都先看看是否超限 -- 堆外内存的限额默认与堆内内存(由-XMX 设定)相仿,可用 -XX:MaxDirectMemorySize 重新设定。
如果已经超限,会主动执行Sytem.gc(),期待能主动回收一点堆外内存。然后休眠一百毫秒,看看totalCapacity降下来没有,如果内存还是不足,就抛出大家最头痛的OOM异常。
如果额度被批准,就调用大名鼎鼎的sun.misc.Unsafe去分配内存,返回内存基地址,Unsafe的C++实现在此,标准的malloc。然后再调一次Unsafe把这段内存给清零。跑个题,Unsafe的名字是提醒大家这个类只给Sun自家用的,你们别用,不然哪天Sun把它藏起来了你们就哭死。果然,JDK9里就Oracle可能动手哦。
JDK7开始,DirectByteBuffer分配内存时默认已不做分页对齐,不会再每次分配并清零 实际需要+分页大小(4k)的内存,这对性能应有较大提升,所以Oracle专门写在了Enhancements in Java I/O里。
最后,创建一个Cleaner,并把代表清理动作的Deallocator类绑定 -- 降低Bits里的totalCapacity,并调用Unsafe调free去释放内存。Cleaner的触发机制后面再说。
2. 堆外内存基于GC的回收
存在于堆内的DirectByteBuffer对象很小,只存着基地址和大小等几个属性,和一个Cleaner,但它代表着后面所分配的一大段内存,是所谓的冰山对象。通过前面说的Cleaner,堆内的DirectByteBuffer对象被GC时,它背后的堆外内存也会被回收。
快速回顾一下堆内的GC机制,当新生代满了,就会发生young gc;如果此时对象还没失效,就不会被回收;撑过几次young gc后,对象被迁移到老生代;当老生代也满了,就会发生full gc。
这里可以看到一种尴尬的情况,因为DirectByteBuffer本身的个头很小,只要熬过了young gc,即使已经失效了也能在老生代里舒服的呆着,不容易把老生代撑爆触发full gc,如果没有别的大块头进入老生代触发full gc,就一直在那耗着,占着一大片堆外内存不释放。
这时,就只能靠前面提到的申请额度超限时触发的system.gc()来救场了。但这道最后的保险其实也不很好,首先它会中断整个进程,然后它让当前线程睡了整整一百毫秒,而且如果gc没在一百毫秒内完成,它仍然会无情的抛出OOM异常。还有,万一,万一大家迷信某个调优指南设置了-DisableExplicitGC禁止了system.gc(),那就不好玩了。
所以,堆外内存还是自己主动点回收更好,比如Netty就是这么做的。
3. 堆外内存的主动回收
对于Sun的JDK这其实很简单,只要从DirectByteBuffer里取出那个sun.misc.Cleaner,然后调用它的clean()就行。
前面说的,clean()执行时实际调用的是被绑定的Deallocator类,这个类可被重复执行,释放过了就不再释放。所以GC时再被动执行一次clean()也没所谓。
在Netty里,因为不确定跑在Sun的JDK里(比如安卓),所以多废了些功夫来确定Cleaner的存在。
4. Cleaner如何与GC相关联?
涨知识的时间到了,原来JDK除了StrongReference,SoftReference 和 WeakReference之外,还有一种PhantomReference,Phantom是幻影的意思,Cleaner就是PhantomReference的子类。
当GC时发现它除了PhantomReference外已不可达(持有它的DirectByteBuffer失效了),就会把它放进 Reference类pending list静态变量里。然后另有一条ReferenceHandler线程,名字叫 "Reference Handler"的,关注着这个pending list,如果看到有对象类型是Cleaner,就会执行它的clean(),其他类型就放入应用构造Reference时传入的ReferenceQueue中,这样应用的代码可以从Queue里拖出这些理论上已死的对象,做爱做的事情——这是一种比finalizer更轻量更好的机制。
5. 其实
专家们说,OpenJDK没有接受jemalloc(redis们在用)的补丁,直接用malloc在OS里申请一段内存,比在已申请好的JVM堆内内存里划一块出来要慢,所以我们在Netty一般用池化的 PooledDirectByteBuf 对DirectByteBuffer进行重用 ,《Netty权威指南》说性能提升了23倍,所以基本不需要头痛堆外内存的释放,顺便还告别了大数据流量下的频繁GC。
片末招聘广告:唯品会广州总部的基础架构部招人!! 如果你喜欢纯技术的工作,对大型互联网企业的服务化平台有兴趣,愿意在架构的成长期还可以大展拳脚的时候加盟,请电邮 calvin.xiao@vipshop.com
文章持续修订,转载请保留原链接: http://calvin1978.blogcn.com/articles/directbytebuffer.html
相关推荐
CPU的缓存是内存的一部分,用于存储频繁访问的数据,提高数据读取速度。一级缓存(L1 Cache)和二级缓存(L2 Cache)的大小对CPU性能有很大影响。通常,更大的缓存容量意味着更好的性能表现。 文章中提到了两个常用...
本篇文章将深入探讨操作系统实验5中涉及的页面置换算法,特别针对FIFO(先进先出)和LRU(最近最少使用)两种常见算法进行分析。 首先,需要明确页面置换算法的目的。在计算机系统运行过程中,由于物理内存空间的...
转自:http://www.witimes.com/ddr-layout-rules-processes/ 多年前,无线时代(Witimes)发布了一篇文章关于DDR布线指导的一篇文章,当时在网络上... DDR SDRAM布线规则 lifan_3alifan_3a2014-11-03 15:57:481052 ...
根据给定的信息,本篇文章将深入探讨如何使用C语言实现三种经典的页面置换算法:先进先出(FIFO)、最佳置换(OPT)与最近最久未使用(LRU),并在特定的页面访问序列和物理块数量条件下计算每种算法的缺页率。...
本篇文章将深入解析市面上几种主流的双核处理器,包括nVIDIA的Tegra 2、德州仪器的OMAP4系列以及高通的MSM8x60,帮助读者更好地了解这些处理器的特点及其在实际应用中的差异。 #### nVIDIA Tegra 2 nVIDIA Tegra 2...
这篇文章对MUSIC(Multiple Signal Classification)算法进行了深入的分析,将其与另外两种常用的到达方向估计(DOA)算法Root-MUSIC和ESPRIT进行了比较。这三种算法在无线通信领域都扮演着重要的角色,它们能够显著...
该文档系列的首篇文章介绍了Sparrow OS的概览、架构、获取方式、模拟器使用、工具链、编译及运行方法。 首先,Sparrow OS是一款专为嵌入式系统设计的操作系统,它目前仅支持ARM S3C6410X架构,属于宏内核系统。它...
本篇文章将结合提供的拆机手册,详细介绍这两个型号的内部结构、关键部件以及如何进行安全拆解和维护。 一、ThinkPad R400 & T400 的主要区别 1. 外观设计:R400 通常比 T400 更注重性价比,因此在材质和细节处理上...
对于小规模数据或者部分有序的数据,插入排序有很好的表现,其时间复杂度在最好情况下(即输入已排序)为O(n),最坏和平均情况均为O(n^2)。 3. 归并排序: 归并排序是一种采用分治策略的排序算法,它将大问题分解为...
同时,文章中提及的优化方法和经验,对于其他类似语音压缩算法的DSP实现,也具有很好的借鉴意义。随着通信设备向着更高性能和更低功耗方向发展,G.729A算法和类似的高效语音编码技术的应用前景将更加广阔。
本篇文章旨在介绍如何将两个已经排好序的单链表合并成一个新的有序链表。 #### 算法思想与步骤 为了实现两个有序链表的合并,我们需要遵循以下步骤: 1. **初始化指针**: - `pa` 和 `pb` 分别指向 `La` 和 `Lb`...
个好的开端。 第一讲 Linux基础 在这一讲中,我们主要是了解一下 Linux 的概况,以及对 Linux 有一个初步的感性认识。 一.什么是Linux? Linux 是一个以 Intel 系列 CPU(CYRIX,AMD 的 CPU也可以)为硬件平台...
本篇文章将深入探讨几种常见的排序算法在Java中的实现,并通过实验对比它们的性能差异。 1. **直接插入排序**: 直接插入排序是一种简单的排序算法,它的工作原理类似于我们日常生活中整理卡片的方式。在未排序...
在本篇文章中,我们将深入探讨如何通过代码方式调整 datagrid 中单元格的宽度。这个问题源自一个具体的编程场景:用户希望动态地控制 datagrid 内各列的宽度,以实现更佳的数据展示效果或适应不同的界面布局需求。...
结果,数据库越来越大,已经差不多60多兆了,如果某篇文章保存的内容多的话,加载的速度非常慢,并不是程序的问题,而是字段内容太大了,Access数据库必须先读入到内存里,才能把字段的值给我。考虑之后决定采用压缩...
总结来说,这篇文章详细地探讨了PASCAL语言中的指针与链表操作,揭示了动态数据结构的教学难点,并通过具体的编程示例和图表说明了指针如何在链表建立过程中动态分配内存。这些知识对于理解现代编程语言中的类似概念...
网管教程 从入门到精通软件篇 ★一。★详细的xp修复控制台命令和用法!!! 放入xp(2000)的光盘,安装时候选R,修复! Windows XP(包括 Windows 2000)的控制台命令是在系统出现一些意外情况下的一种非常有效的...
本篇文章主要基于一组关于ADO.NET 2.0的模拟试题进行分析,重点在于理解ADO.NET 2.0的数据访问机制,包括非连接对象的概念、数据提供程序的选择与性能比较以及连接池的管理等内容。 #### 二、非连接对象概念 题目中...
本篇文章将详细介绍山东大学单片机软件实验报告3.1,该报告主要讲述了基本并行I/O口实验的设计与实现。 实验的核心目标是通过单片机控制D1-D8这八个LED灯轮流点亮,每盏灯的点亮时间为100毫秒。当按下SW1按键时,...
他保存到你的电脑中或者把这篇文章的地址收藏到收藏夹里。 1、如何实现关机时清空页面文件 打开“控制面板”,单击“管理工具→本地安全策略→本地策略→安全 选项”,双击其中“关机:清理虚拟内存页面文件...