nio DirectByteBuffer如何回收堆外内存
概述
使用了nio框架的应用,比如服务框架,利用nio建立长连接通信,他们会使用DirectByteBuffer来分配堆外内存,也就是本地直接内存,这个内存的回收不由gc直接维护,我们通常所说的gc,只回收jvm的堆、方法区。本地内存如果没有用jvm启动参数手动指定,它会根据主机的剩余可用内存进行分配,如果说一个机器的8G内存的,其中,我们手动指定的jvm堆、方法区内存为2048 + 256,那么,除了其他进程占用的内存,剩余的可用内存可能是较大的。如果你的主机有内存使用量监控(不是jvm级的内存监控),在使用类似Netty这种通信框架时,有可能会触发主机内存使用率报警。
堆外内存回收方法
首先强调,gc不会直接回收堆外内存,堆外内存如果不通过启动参数指定,会根据主机的剩余可用内存来作为容量,这有可能是一块很大的内存,gc回收代价可能较大
DirectByteBuffer对象在堆内生成时,会和一个RefereceQueue建立虚引用联系,这里是通过Cleaner对象的某个field被赋值为DirectByteBuffer对象来建立虚引用的,注意,这里是Cleaner对象虚引用了DirectByteBuffer对象,引用queue为cleaner中的dummyQueue。再次强调,cleaner对象虽然虚引用了DirectByteBuffer,但是垃圾回收在计算对象可达性时,会忽略Cleaner对的DirectByteBuffer虚引用,DirectByteBuffer只有可以,就可以立即回收,无视Cleaner对象当前是存活状态。
jdk实现GC时,对几种引用类型有定制化开发,在对象B被回收后,会通知到一个ReferenceHandler线程,获取到虚引用对象A,判断A是否是Cleaner,如果是就会调用Cleaner.clean()方法,获取DirectByteBuffer被分配的堆外内存地址,释放B在堆外内存开辟的空间,释放内存
class DirectByteBuffer{
// Primary constructor
//
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);
} 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; }
...
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);
} }
...
}
Clean类,runnable这里对应Deallocator类型
package sun.misc;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.security.AccessController;
import java.security.PrivilegedAction;
public class Cleaner extends PhantomReference<Object> {
private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue();
private static Cleaner first = null;
private Cleaner next = null;
private Cleaner prev = null;
private final Runnable thunk;
private static synchronized Cleaner add(Cleaner var0) {
if(first != null) {
var0.next = first;
first.prev = var0;
}
first = var0;
return var0;
}
private static synchronized boolean remove(Cleaner var0) {
if(var0.next == var0) {
return false;
} else {
if(first == var0) {
if(var0.next != null) {
first = var0.next;
} else {
first = var0.prev;
}
}
if(var0.next != null) {
var0.next.prev = var0.prev;
}
if(var0.prev != null) {
var0.prev.next = var0.next;
}
var0.next = var0;
var0.prev = var0;
return true;
}
}
private Cleaner(Object var1, Runnable var2) {
super(var1, dummyQueue);
this.thunk = var2;
}
public static Cleaner create(Object var0, Runnable var1) {
return var1 == null?null:add(new Cleaner(var0, var1));
}
public void clean() {
if(remove(this)) {
try {
this.thunk.run();
} catch (final Throwable var2) {
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
if(System.err != null) {
(new Error("Cleaner terminated abnormally", var2)).printStackTrace();
}
System.exit(1);
return null;
}
});
}
}
}
}
当jvm回收掉DirectByteBuffer后,会将虚引用它的对象Cleaner入队ReferenceHandler中会消费的队列,同时ReferenceHandler线程发现新来了一个虚引用对象,会判断这个对象否为Cleaner,如果是,则会执行clean()方法,达到回收的目的
/* High-priority thread to enqueue pending References
*/
private static class ReferenceHandler extends Thread {
ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}
public void run() {
for (;;) {
Reference<Object> r;
synchronized (lock) {
if (pending != null) {
r = pending;
pending = r.discovered;
r.discovered = null;
} else {
// The waiting on the lock may cause an OOME because it may try to allocate
// exception objects, so also catch OOME here to avoid silent exit of the
// reference handler thread.
//
// Explicitly define the order of the two exceptions we catch here
// when waiting for the lock.
//
// We do not want to try to potentially load the InterruptedException class
// (which would be done if this was its first use, and InterruptedException
// were checked first) in this situation.
//
// This may lead to the VM not ever trying to load the InterruptedException
// class again.
try {
try {
lock.wait();
} catch (OutOfMemoryError x) { }
} catch (InterruptedException x) { }
continue;
}
}
// Fast path for cleaners
if (r instanceof Cleaner) {
((Cleaner)r).clean();
continue;
}
ReferenceQueue<Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
}
}
}
nio DirectByteBuffer如何回收堆外内存的更多相关文章
- 【Java】 DirectByteBuffer堆外内存回收
PhantomReference虚引用 在分析堆外内存回收之前,先了解下PhantomReference虚引用. PhantomReference需要与ReferenceQueue引用队列结合使用,在 ...
- Java堆外内存之三:堆外内存回收方法
一.JVM内存的分配及垃圾回收 对于JVM的内存规则,应该是老生常谈的东西了,这里我就简单的说下: 新生代:一般来说新创建的对象都分配在这里. 年老代:经过几次垃圾回收,新生代的对象就会放在年老代里面 ...
- JVM源码分析之堆外内存完全解读
JVM源码分析之堆外内存完全解读 寒泉子 2016-01-15 17:26:16 浏览6837 评论0 阿里技术协会 摘要: 概述 广义的堆外内存 说到堆外内存,那大家肯定想到堆内内存,这也是我们 ...
- Netty堆外内存泄漏排查,这一篇全讲清楚了
上篇文章介绍了Netty内存模型原理,由于Netty在使用不当会导致堆外内存泄漏,网上关于这方面的资料比较少,所以写下这篇文章,专门介绍排查Netty堆外内存相关的知识点,诊断工具,以及排查思路提供参 ...
- Java堆外内存之突破JVM枷锁
对于有Java开发经验的朋友都知道,Java中不需要手动的申请和释放内存,JVM会自动进行垃圾回收:而使用的内存是由JVM控制的. 那么,什么时机会进行垃圾回收,如何避免过度频繁的垃圾回收?如果JVM ...
- Spring Boot引起的“堆外内存泄漏”排查及经验总结
小结: 检索词:C++内存分配器.jvm内存模型.gdb.内存泄露 https://tech.meituan.com/2019/01/03/spring-boot-native-memory-leak ...
- 【转载】Spring Boot引起的“堆外内存泄漏”排查及经验总结
背景 为了更好地实现对项目的管理,我们将组内一个项目迁移到MDP框架(基于Spring Boot),随后我们就发现系统会频繁报出Swap区域使用量过高的异常.笔者被叫去帮忙查看原因,发现配置了4G堆内 ...
- Spring Boot引起的“堆外内存泄漏”排查及经验总结 strace
小结: 检索词:C++内存分配器.jvm内存模型.gdb.内存泄露 https://tech.meituan.com/2019/01/03/spring-boot-native-memory-leak ...
- Java堆外内存的使用
堆外内存的回收见HeapByteBuffer和DirectByteBuffer以及回收DirectByteBuffer 基本类型长度 在Java中有很多的基本类型,比如: byte,一个字节是8位bi ...
随机推荐
- Java新特性:数据类型可以扔掉了?
在很久很久以前,我们写代码时要慎重的考虑变量的数据类型,比如下面这些: 枚举:尽管在 JDK 5 中增加了枚举类型,但是 Class 文件常量池的 CONSTANT_Class_info 类型常量并没 ...
- (一)jenkins+git+docker
目录 一.发布流程设计 1.工作流程 2.环境规划如下 二.部署Git仓库 1.创建Git用户并设置密码 2.创建仓库 3.免密访问 4.在jenkins那台机上做ssh认证 三.准备Jenkins环 ...
- Spring Environment对象获取属性
String[] activeProfiles = env.getActiveProfiles();//获取当前是启用哪一个个配置文件 System.out.println(Arrays.toStri ...
- JELLY技术周刊 Vol.24 -- 技术周刊 · 实现 Recoil 只需百行代码?
蒲公英 · JELLY技术周刊 Vol.24 理解一个轮子最好的方法就是仿造一个轮子,很多框架都因此应运而生,比如面向 JS 开发者的 AI 工具 Danfo.js:参考 qiankun 的微前端框架 ...
- Python-全局解释器锁GIL原理和多线程产生原因与原理-多线程通信机制
GIL 全局解释器锁,这个锁是个粗粒度的锁,解释器层面上的锁,为了保证线程安全,同一时刻只允许一个线程执行,但这个锁并不能保存线程安全,因为GIL会释放掉的并且切换到另外一个线程上,不会完全占用,依据 ...
- Python练习题 007:兔子生兔子
[Python练习题 007] 有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? ----------------- ...
- Intel HEX格式
来来 !! come baby ! 只强调一点这篇文章有checksum的算法,是我最喜欢地!! 参考:https://blog.csdn.net/extlife/article/details/ ...
- 《C++primerplus》第7章练习题
1.用户不断输入两个数,计算调和平均数,直到其中一个数为0. #include<iostream> using namespace std; double harm_mean(double ...
- Flutter 开发从 0 到 1(三)布局与 ListView
上周日出去玩了,因此没时间写文章.我司加班到 11 点,第二天可以晚上班一个小时,加班到 12 点,可以晚上班两个小时,以此类推,为什么说这个,对的,加班第二天我没有多睡觉,而是起来抓紧时间写文章,好 ...
- JavaScript DOM三种创建元素的方式
三种创建元素的方式: document.write() element.innerHTML document.createElement() 初始HTML内容: <button>btn&l ...