- 921. Minimum Add to Make Parentheses Valid 使括号有效的最少添加
- 915. Partition Array into Disjoint Intervals 分割数组
- 932. Beautiful Array 漂亮数组
- 940. Distinct Subsequences II 不同的子序列 II
在介绍OOM那篇文章中,对堆外内存进行了介绍,就直接把它复制过来;
ByteBuffer和DirectByteBuffer:
ByteBuffer:字节缓冲区,它有两种实现:
HeapByteBuffer:使用jvm堆内存的字节缓冲区;(对应 ByteBuffer源码中的 allocate()方法)
DirectByteBuffer:使用堆外内存,不受jvm堆大小限制;(对应 ByteBuffer源码中的allocateDirect()方法)
DirectByteBuffer:ByteBuffer对于使用堆外内存的实现,堆外内存直接使用unsafe方法请求堆外内存空间,读写数据;
源码:
public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer> {
// ... 省略
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
// ... 省略
}
DirectByteBuffer与堆外内存的关系:
堆外内存空间大小设置:
public class VM {
private static long directMemory = 64 * 1024 * 1024;
public static long maxDirectMemory() {
return directMemory;
}
}
也就是说堆外内存默认大小为64M;如果指定了jvm参数,就使用指定的大小值;
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1 * 1024 * 1024);
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
在使用ByteBuffer的静态方法allocateDirect()申请内存时,会使用 DirectByteBuffer类的构造方法创建一个DirectByteBuffer对象:
DirectByteBuffer(int cap) {
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
// 判断是否有足够的空间可供申请
// size:根据是否按页对齐,得到的真实需要申请的内存大小
// cap:用户指定需要的内存大小(<=size)
Bits.reserveMemory(size, cap);
long base = 0;
try {
// 调用 UNsafe方法申请内存
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
// 申请失败,释放内存
Bits.unreserveMemory(size, cap);
throw x;
}
// 初始化内存空间为0
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 = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
在使用构造方法申请内存时,首先要判断是否有足够的内存空间可供申请,如果有空间可以申请,则更新对应的变量;如果不够需要先调用GC,如果还不够则抛出OOM;
static void reserveMemory(long size, int cap) {
if (!memoryLimitSet && VM.isBooted()) {
// 获取最大可以申请的对外内存大小,默认值是64MB
// 可通过jvm参数-XX:MaxDirectMemorySize=<size>设置这个大小
maxMemory = VM.maxDirectMemory();
memoryLimitSet = true;
}
// optimist!
// 尝试去预订空间,如果空间足够,则直接返回true
if (tryReserveMemory(size, cap)) {
return;
}
final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();
// retry while helping enqueue pending Reference objects
// which includes executing pending Cleaner(s) which includes
// Cleaner(s) that free direct buffer memory
// 可能 Cleaner会释放空间,进行重试,继续尝试预订空间
while (jlra.tryHandlePendingReference()) {
if (tryReserveMemory(size, cap)) {
return;
}
}
// trigger VM's Reference processing
// 还不够的话,在这里调用 GC,进行一次空间回收,释放指向了堆外内存的引用
System.gc();
// a retry loop with exponential back-off delays
// (this gives VM some time to do it's job)
// 通过一个带sleep的延时循环来继续尝试预订空间,预订成功则返回;
// 如果超过 MAX_SLEEPS 次数还未成功,直接抛出 "Direct buffer memory" 异常
boolean interrupted = false;
try {
long sleepTime = 1;
int sleeps = 0;
while (true) {
if (tryReserveMemory(size, cap)) {
return;
}
if (sleeps >= MAX_SLEEPS) {
break;
}
if (!jlra.tryHandlePendingReference()) {
try {
Thread.sleep(sleepTime);
sleepTime <<= 1;
sleeps++;
} catch (InterruptedException e) {
interrupted = true;
}
}
}
// no luck
// 几次重试预订都还不够的话,抛出 OOM
throw new OutOfMemoryError("Direct buffer memory");
} finally {
if (interrupted) {
// don't swallow interrupts
Thread.currentThread().interrupt();
}
}
}
在执行完这个方法之后,也就是有空间可以申请了,就开始申请空间:
try {
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
allocateMemory()这个方法是通过unsafe通过JNI调用C的malloc来申请内存的;
在上面申请完堆外内存之后,注册了一个 Cleaner的内存回收对象:
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
这里是因为 DirectByteBuffer对象本身在堆内,会被jvm的GC正常回收掉;但是跟DirectByteBuffer关联的堆外内存区域就不会被GC回收掉了,所以需要一个机制,来在DirectByteBuffer被回收时,同时回收掉它在堆外申请的内存;
对于一个对象在被回收时,如果需要做一些额外的工作,java提供了两个特性来实现:
在堆外内存的释放这里,java采用的是虚引用的方法;并且提供了一个 Cleaner 类来简化这些操作,Cleaner是 PhantomReference的子类,可以在 PhantomReference被加入 ReferenceQueue队列的时候,触发对应的Runnable回调方法;
new Deallocator(base, size, cap);
这里的Deallocator就是一个实现了 Runnable接口的类:
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方法释放了内存并重置了统计变量;
所以,DirectByteBuffer 就是使用 Cleaner机制,把它自身当做一个虚引用对象来创建了一个 PhantomReference;所以当它本身被GC时,会添加到引用队列中;
然后通过对这个引用队列的监测,来调用它里面的对象的实现了 Runnable接口的Deallocator类的 run()方法,来实现堆外内存的释放。
通过上面的介绍,我们知道了堆外内存的分配和回收的具体实现,那什么情况下会发生堆外内存不够,甚至OOM呢?
1、 如果系统瞬时承受了大量的并发请求,创建了大量的DirectByteBuffer,并且处理又慢,没法及时被GC掉,自然堆外内存不会被释放,然后就导致堆外内存溢出了;
2、 由于系统的jvm内存区域划分不够合理,在发生了Eden区的YGC之后,Survivor区放不下存活的对象,这些存活对象就会进入老年代;
然而正常来说老年代会很久发生一次FGC,也就是说,进入了老年代的 DirectByteBuffer 对象很久都不会被回收,自然堆外内存不会被释放,然后就导致堆外内存溢出了; 3、 对于第2种情况,其实jvm已经考虑到了;所以它才在申请内存不够时,会代码调用一次System.gc()来手动触发GC;
但是,如果你使用了 -XX:+DisableExplicitGC
参数,来关闭了显示GC呢;
所以,如果使用了 -XX:+DisableExplicitGC
参数来关闭了显示GC,发生第2种情况的时候,就会导致 堆外内存溢出了;
/**
*
* 直接内存溢出
*
* jvm options:
* -XX:MaxDirectMemorySize=100m -verbose:gc -XX:+PrintGCDetails
*/
public class DirectByteBufferOomDemo {
public static void main(String[] args) {
int count = 0;
List<ByteBuffer> list = new ArrayList<>();
while (true) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1 * 1024 * 1024);
list.add(byteBuffer);
System.out.println("当前创建了 " + (++count) + "M 的对象");
}
}
}
代码很简单,就在一个死循环里面一直申请大小为1M的 DirectByteBuffer 对象,然后加入list,也就是有一个list一直持有它的引用,不会被GC掉;
然后使用了 -XX:MaxDirectMemorySize=100m
指定堆外内存最大值为 100M,所以应该在申请快到100M的时候,发生堆外内存OOM;
看看执行情况:
可以看到,这里生成了100M对象,并且继续生成的时候,堆外内存区域不够了,然后是先去执行 System.gc();
但是由于对象被引用持有没法被GC掉,所以就发生了 java.lang.OutOfMemoryError: Direct buffer memory
;
/**
*
* 直接内存溢出
*
* jvm options:
* -XX:MaxDirectMemorySize=100m -verbose:gc -XX:+PrintGCDetails
*/
*/
public class DirectByteBufferOomDemo {
public static void main(String[] args) {
int count = 0;
// List<ByteBuffer> list = new ArrayList<>();
while (true) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1 * 1024 * 1024);
// list.add(byteBuffer);
System.out.println("当前创建了 " + (++count) + "M 的对象");
}
}
}
跟模拟1不同是,注释掉list的代码,让申请的 DirectByteBuffer对象没有引用指向,也就是会成为垃圾对象;
在申请堆外内存不足时,DirectByteBuffer中手动调用 System.gc()的时候,会回收掉它们,所以这里不应该发生堆外内存OOM;
看看执行情况:
可以看到,每生成100M对象的时候,执行 System.gc();
由于对象是垃圾对象可以被回收,在GC之后就又可以继续生成,所以并没有发生 java.lang.OutOfMemoryError: Direct buffer memory
;
/**
*
* 直接内存溢出
*
* jvm options:
* -XX:MaxDirectMemorySize=100m -verbose:gc -XX:+PrintGCDetails -XX:+DisableExplicitGC
*/
public class DirectByteBufferOomDemo {
public static void main(String[] args) {
int count = 0;
// List<ByteBuffer> list = new ArrayList<>();
while (true) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1 * 1024 * 1024);
// list.add(byteBuffer);
System.out.println("当前创建了 " + (++count) + "M 的对象");
}
}
}
跟模拟2不同的是,添加了jvm参数 -XX:+DisableExplicitGC
,这个参数禁止代码中的显示 System.gc()操作;
所以在堆外内存满的时候,执行System.gc()无效,不能回收掉已经是垃圾对象了的DirectByteBuffer对象,也就不能释放掉堆外内存,应该会发生堆外内存OOM;
看看执行情况:
可以看到,在生成了100M对象的时候,发生了 java.lang.OutOfMemoryError: Direct buffer memory
;
同样,遇到系统崩溃的时候,首先还是登陆服务器查看系统日志,这个时候会看到 java.lang.OutOfMemoryError: Direct buffer memory
这样的报错,也就知道了这是堆外内存的OOM;
在发生堆外内存OOM的时候,即使你配置了dump开关,它也不会在系统发生崩溃时生成dump日志(这个很明显,堆外内存都没有在堆中,肯定不会给你生成堆栈的dump日志);
但是这个报错的下方,会携带上 真正引发堆外内存OOM 的具体类和方法及里面的代码行数;
这样你就能知道,是你在代码中手动生成堆外内存导致的,还是一些框架(比如jetty、netty等都会)里面生成堆外内存导致的;
如果是你在代码手动生成堆外内存导致的,那你就需要去仔细检查你的代码是否有问题,是否生成了太多的 DirectByteBuffer并且不能被回收;
如果你的代码没有这种问题,或者是框架里面引发的问题,那就得从GC去考虑了:
首先查看是否配置了 -XX:+DisableExplicitGC
这个限制;
如果不是,那就有点复杂了;需要使用我们前面介绍到的 jstat工具,去判断jvm内存区域大小分配是否有问题;
实战-行业攻防应急响应 简介: 服务器场景操作系统 Ubuntu 服务器账号密码:root/security123 分析流量包在/home/security/security.pcap 相
背景 最近公司将我们之前使用的链路工具切换为了 OpenTelemetry. 我们的技术栈是: OTLP C
一 同一类的方法都用 synchronized 修饰 1 代码 package concurrent; import java.util.concurrent.TimeUnit; public c
一 简单例子 1 代码 package concurrent.threadlocal; /** * ThreadLocal测试 * * @author cakin */ public class T
1. 问题背景 问题发生在快递分拣的流程中,我尽可能将业务背景简化,让大家只关注并发问题本身。 分拣业务针对每个快递包裹都会生成一个任务,我们称它为 task。task 中有两个字段需要
实战环境 elastic search 8.5.0 + kibna 8.5.0 + springboot 3.0.2 + spring data elasticsearch 5.0.2 +
Win10下yolov8 tensorrt模型加速部署【实战】 TensorRT-Alpha 基于tensorrt+cuda c++实现模型end2end的gpu加速,支持win10、
yolov8 tensorrt模型加速部署【实战】 TensorRT-Alpha 基于tensorrt+cuda c++实现模型end2end的gpu加速,支持win10、linux,
目录如下: 为什么需要自定义授权类型? 前面介绍OAuth2.0的基础知识点时介绍过支持的4种授权类型,分别如下: 授权码模式 简化模式 客户端模式 密码模式
今天这篇文章介绍一下如何在修改密码、修改权限、注销等场景下使JWT失效。 文章的目录如下: 解决方案 JWT最大的一个优势在于它是无状态的,自身包含了认证鉴权所需要的所有信息,服务器端
前言 大家好,我是捡田螺的小男孩。(求个星标置顶) 我们日常做分页需求时,一般会用limit实现,但是当偏移量特别大的时候,查询效率就变得低下。本文将分四个方案,讨论如何优化MySQL百万数
前言 大家好,我是捡田螺的小男孩。 平时我们写代码呢,多数情况都是流水线式写代码,基本就可以实现业务逻辑了。如何在写代码中找到乐趣呢,我觉得,最好的方式就是:使用设计模式优化自己
我们先讲一些arm汇编的基础知识。(我们以armv7为例,最新iphone5s上的64位暂不讨论) 基础知识部分: 首先你介绍一下寄存器: r0-r3:用于函数参数及返回值的传递 r4-r6
一 同一类的静态方法都用 synchronized 修饰 1 代码 package concurrent; import java.util.concurrent.TimeUnit; public
DRF快速写五个接口,比你用手也快··· 实战-DRF快速写接口 开发环境 Python3.6 Pycharm专业版2021.2.3 Sqlite3 Django 2.2 djangorestfram
一 添加依赖 org.apache.thrift libthrift 0.11.0 二 编写 IDL 通过 IDL(.thrift 文件)定义数据结构、异常和接口等数据,供各种编程语言使用 nam
我正在阅读 Redis in action e-book关于semaphores的章节.这是使用redis实现信号量的python代码 def acquire_semaphore(conn, semn
自定义控件在WPF开发中是很常见的,有时候某些控件需要契合业务或者美化统一样式,这时候就需要对控件做出一些改造。 目录 按钮设置圆角
师父布置的任务,让我写一个服务练练手,搞清楚socket的原理和过程后跑了一个小demo,很有成就感,代码内容也比较清晰易懂,很有教育启发意义。 代码 ?
? 1 2
我是一名优秀的程序员,十分优秀!