Java NIO为何导致堆外内存OOM了?

news2025/7/27 18:16:30

Java NIO为何导致堆外内存OOM了?

描述

  • 某天报警:某台机器部署的一个服务突然无法访问。谨记第一反应登录机器查看日志,因为服务挂掉,很可能因OOM。这个时候在机器的日志中发现了如下的一些信息:

  • nio handle failed java.lang.OutOfMemoryError: Direct buffer memory at org.eclipse.jetty.io.nio.xxxx
    at org.eclipse.jetty.io.nio.xxxx at org.eclipse.jetty.io.nio.xxxx
    
  • 表明确实为OOM,问题是哪个区导致的呢?可以看到是:Direct buffer memory,还看到一大堆jetty相关方法调用栈,仅凭这些日志,就能分析OOM原因。

Direct buffer memory

  • 堆外内存,JVM堆内存之外的一块内存,不是由JVM管理,但Java代码却能在JVM堆外使用一些内存空间。这些空间就是Direct buffer memory,即直接内存,这块内存由os直接管理。但称其为直接内存有些奇怪,我没更爱称其为“堆外内存”。

  • Jetty作为JVM进程运行我们写好的系统的流程:

  • 在这里插入图片描述

  • 这次OOM是Jetty在使用堆外内存时导致。可推算得,Jetty可能在不停使用堆外内存,然后堆外内存空间不足,没法使用更多堆外内存,就OOM了。

  • Jetty不停使用堆外内存:

  • 在这里插入图片描述

解决OOM的底层技术

  • Jetty既然是用Java写的,那他是如何通过Java代码申请堆外内存的?然后这个堆外内存空间又如何释放呢?这涉及Java的NIO底层。
  • JVM的性能优化相对还是较为容易一些的,但若是解决OOM,除了一些弱智和简单的,如有人在代码里不停创建对象。其他很多生产的OOM问题,都有点技术难度,需要扎实技术。

堆外内存是如何申请的,又是如何释放的?

  • 如在Java代码里要申请使用一块堆外内存空间,是使用DirectByteBuffer这个类,你可以通过这个类构建一个DirectByteBuffer的对象,这个对象本身是在JVM堆内存里的。

  • 但是你在构建这个对象的同时,就会在堆外内存中划出来一块内存空间跟这个对象关联起来,我们看看下面的图,你就对他们俩的关系很清楚了。

  • 在这里插入图片描述

  • 因此在分配堆外内存时,基本就这思路。

如何释放堆外内存

  • 当你的DirectByteBuffer对象无人引用,成垃圾后,就会在某次YGC或Full GC时被回收。
  • 只要回收一个DirectByteBuffer对象,就会释放其关联的堆外内存:
  • 在这里插入图片描述

那为何还出现堆外内存溢出?

  • 若你创建很多DirectByteBuffer对象,占了大量堆外内存,然后这些DirectByteBuffer对象还无GC线程来回收,那就不会释放呀!
  • 当堆外内存都被大量DirectByteBuffer对象关联使用,若你再要使用额外堆外内存,就报内存溢出!何时会出现大量DirectByteBuffer对象一直存活,导致大量堆外内存无法释放?
  • 还可能是系统高并发,创建过多DirectByteBuffer,占用大量堆外内存,此时再继续想要使用堆外内存,就会OOM!但该系统显然不是这种情况。

真正的堆外内存溢出原因

  • 可以用jstat观察线上系统运行情况,同时根据日志看看一些请求的处理耗时,分析过往gc日志,还看了一下系统各个接口的调用耗时后,分析思路如下。

  • 首先看接口调用耗时,系统并发量不高,但他每个请求处理较耗时,平均每个请求需1s。

  • 然后jstat发现,随系统不停被调用,会一直创建各种对象,包括Jetty本身不停创建DirectByteBuffer对象去申请堆外内存空间,接着直到Eden满,就会触发YGC:

  • 在这里插入图片描述

  • 但往往在进行GC的一瞬间,可能有的请求还没处理完,此时就有不少DirectByteBuffer对象处于存活状态,还没被回收,当然之前不少DirectByteBuffer对象对应的请求可能处理完毕了,他们就可以被回收了。

  • 此时肯定会有一些DirectByteBuffer对象以及一些其他的对象是处于存活状态的,就需转入Survivor区。记得该系统上线时,内存分配极不合理,就给了年轻代一两百M,老年代却给七八百M,导致年轻代中的Survivor只有10M。因此往往在YGC后,一些存活下的对象(包括了一些DirectByteBuffer)会超过10M,没法放入Survivor,直接进入Old:

  • 在这里插入图片描述

  • 于是反复的执行这样的过程,导致一些DirectByteBuffer对象慢慢进入Old,Old的DirectByteBuffer 对象越来越多,而且这些DirectByteBuffer都关联很多堆外内存:

  • 在这里插入图片描述

  • 这些老年代里的DirectByteBuffer其实很多都是可以回收的状态了,但是因为老年代一直没塞满,所以没触发full gc,也就自然不会回收老年代里的这些DirectByteBuffer了!当然老年代里这些没有被回收的DirectByteBuffer就一直关联占据了大量的堆外内存空间了!

  • 直到最后,当你要继续使用堆外内存时,所有堆外内存都被老年代里大量的DirectByteBuffer给占用了,虽然他们可以被回收,但是无奈因为始终没有触发老年代的full gc,所以堆外内存也始终无法被回收掉。最后导致OOM!

这Java NIO怎么看起来这么沙雕?

  • Java NIO没考虑过会发生这种事吗?

  • 考虑了!他知道可能很多DirectByteBuffer对象也许没人用了,但因未触发gc就导致他们一直占据堆外内存。Java NIO做了如下处理,每次分配新的堆外内存时,都调用System.gc(),提醒JVM主动执行以下GC,去回收掉一些垃圾没人引用的DirectByteBuffer对象,释放堆外内存空间。

  • 只要能触发GC去回收掉一些没人引用的DirectByteBuffer,就会释放一些堆外内存,自然就可以分配更多对象到堆外内存。但因为我们又在JVM设置了:

  • -XX:+DisableExplicitGC
    
  • 导致这System.gc()不生效,因此导致OOM。

终极优化

  • 项目有如下问题:
  • 内存设置不合理,导致DirectByteBuffer对象一直慢慢进入老年代,堆外内存一直无法释放
  • 设置了-XX:+DisableExplicitGC,导致Java NIO无法主动提醒去回收掉一些垃圾DIrectByteBuffer对象,也导致了无法释放堆外内存
  • 对此就该:
  • 合理分配内存,给年轻代更多内存,让Survivor区域有更大的空间
  • 放开-XX:+DisableExplicitGC这个限制,让System.gc()生效
  • 优化后,DirectByteBuffer一般就不会不断进入老年代了。只要他停留在年轻代,随着young gc就会正常回收释放堆外内存了。
  • 只要放开-XX:+DisableExplicitGC限制,Java NIO发现堆外内存不足了,自然会通过System.gc()提醒JVM去主动垃圾回收,回收掉一些DirectByteBuffer,进而释放堆外内存。

-----------------------------------------------------------------------------------

offer突击训练营简介:

1:针对不知道怎么面试,面试没有信心的小伙伴,我们会给你一个offer保障。

2:我们会监督你15-20天内把面试体系技术点掌握至少7成,这样足够你去找到满意的工作了。

3:我们是面向面试学习指导,不会带你们去写代码,会把项目真实开发的迭代过程和技术细节如何实现业务功能都详细教清楚,你能在面试中流畅表达清楚就行了,项目经验你不用担心(技术老师提供的真实项目经验肯定拿的出手),自己学和别人带着系统学,效率完全不一样。

详情请点击这里:offer突击训练营,给你一个offer的保障,求职跳槽的看过来!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1156727.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小程序视频编辑SDK技术解决方案

传统的视频制作方式不仅耗时耗力,而且难以满足企业多样化、个性化的定制需求。为了帮助企业解决这一难题,美摄科技推出了一款专为企业量身定制的小程序视频编辑SDK技术解决方案,让您的视频制作更加高效、专业! 一、功能强大&…

用前端框架Bootstrap的AdminLTE模板和Django实现后台首页的页面

承接博文 用前端框架Bootstrap和Django实现用户注册页面 继续开发实现 后台首页的页面。 01-下载 AdminLTE-3.1.0-rc 并解压缩 以下需要的四个文件夹及里面的文件百度网盘下载链接: https://pan.baidu.com/s/1QYpjOfSBJPmjmVuFZdSgFQ?pwdo9ta 下载 AdminLTE-3.1…

【SpringCloud学习笔记(一)】

SpringCloud学习笔记(一) 一、认识SpringCloud1.1 简介1.2 服务与拆分与远程调用1.3 微服务的远程调用 二、微服务的几大组件2.1 EureKa注册中心2.1.1 Eureka介绍:2.1.2 Eureka实践: 2.2 Ribbon负载均衡2.2.1 负载均衡流程2.2.2 负…

Elasticsearch(三)---索引

索引文档的语法curl用法 CURL: 简单认为是可以在命令行下访问url的一个工具 curl是利用URL语法在命令行方式下工作的开源文件传输工具,使用curl可以简单实现常见的get/post请求。 curl -X 指定http请求的方法 GET POST PUT DELETE restfu…

【POI-EXCEL-下拉框】POI导出excel下拉框数据太多导致下拉框不显示BUG修复

RT 最近在线上遇到一个很难受的BUG,我一度以为是我代码逻辑出了问题,用了Arthas定位分析之后,开始坚定了信心:大概率是POI的API有问题,比如写入数据过多。 PS:上图为正常的下拉框。但是,当下拉…

一文看懂图像格式 RAW、RGB、YUV、Packed/Unpacked、Bayer、MIPI、Planar、Semi-Planar、Interleaved

目录 一、通用属性 1. Packed/Unpacked 2. 压缩/非压缩 二、RAW 1. Bayer格式 2. 分类 3. MIPI RAW 三、RGB 分类 四、YUV 1. YUV与RGB转换 2. 分类 3. 内存计算 五、压缩格式 有的人,错过了,一生再也找寻不到。 本文详细分析各种图像格式…

[GDOUCTF 2023]<ez_ze> SSTI 过滤数字 大括号{等

SSTI模板注入-中括号、args、下划线、单双引号、os、request、花括号、数字被过滤绕过(ctfshow web入门370)-CSDN博客 ssti板块注入 正好不会 {%%}的内容 学习一下 经过测试 发现过滤了 {{}} 那么我们就开始吧 我们可以通过这个语句来查询是否存在ss…

鸿蒙切换到主线程

鸿蒙和安卓都是一样的视图操作都需要在主线程或者UI(视图线程)中处理,否则就会报错。 在安卓中可以通过: View.post(new Runnable() {Overridepublic void run() {textView.setText("更新textView");} });runOnUiThread(new Runnable() {public void run…

算法学习打卡day36| 738.单调递增的数字、 968.监控二叉树、贪心算法阶段学习总结

738.单调递增的数字 力扣题目链接 题目描述&#xff1a; 当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。 给定一个整数 n &#xff0c;返回 小于或等于 n 的最大数字&#xff0c;且数字呈 单调递增 。 示例 1: 输入: n 10 …

JVM虚拟机:通过一个例子解释JVM中栈结构的使用

代码 代码解析 main方法执行&#xff0c;创建栈帧并压栈。 int d8&#xff0c;d为局部变量&#xff0c;是基础类型&#xff0c;它位于虚拟机栈的局部变量表中 然后创建了一个TestDemo的对象&#xff0c;这个对象在堆中&#xff0c;并且这个对象的成员变量&#xff08;day&am…

安防视频监控平台EasyCVR(V.3.4)新功能:告警查询操作步骤

视频集中存储/云存储/视频监控管理平台EasyCVR能在复杂的网络环境中&#xff0c;将分散的各类视频资源进行统一汇聚、整合、集中管理&#xff0c;实现视频资源的鉴权管理、按需调阅、全网分发、智能分析等。AI智能大数据视频分析EasyCVR平台已经广泛应用在工地、工厂、园区、楼…

2024王道考研计算机组成原理——中央处理器

CPU的运算器其实就是进行固定的数据处理&#xff0c;后面讲的CPU主要侧重的是它的控制器功能 运算器的基本结构 左右两边都是16位&#xff0c;因为寄存器可能位于左右两端的一边(源/目的操作数) A、B两端都要接一堆线 通用寄存器 ALU都在运算器当中 从主存来的数据直接放到…

3201. 任务调度

有若干个任务需要在一台机器上运行。 它们之间没有依赖关系&#xff0c;因此可以被按照任意顺序执行。 该机器有两个 CPU 和一个 GPU。 对于每个任务&#xff0c;你可以为它分配不同的硬件资源: 在单个 CPU 上运行。在两个 CPU 上同时运行。在单个 CPU 和 GPU 上同时运行。在两…

RabbitMQ消息队列笔记

目录 docker Java 导包 配置文件 Work Queues 消息堆积 消息生产者发送消息到队列 消息消费者接收消息 Fanout交换机 Direct交换机发送消息 用Java代码创建交换机和队列、绑定 Direct交换机 Direct交换机发送消息 用Java代码创建交换机和队列、绑定 基于注解声明队…

Rust 语言常见的一些概念(上)

目录 1、变量的可变性 常量 隐藏 2、数据类型 2.1 标量类型 整型 浮点型 数值运算 布尔型 字符类型 复合类型 元组类型 数组类型 1、变量的可变性 变量默认是不可改变的&#xff08;immutable&#xff09;。这是 Rust 提供给你的众多优势之一&#xff0c;让你得以…

win32 读写UTF-8格式的文件的方法

1&#xff0c;写入数据 最开始是在写入数据前先写入三个字节 BYTE btHead[] { 0xEF,0xBB,0xBF }; ::WriteFile(hFile, btHead, 3, &dwWrite, 0); ::WriteFile(hFile, str, lstrlen(str)*sizeof(TCHAR), &dwWrite, 0);这样写入后文件样式为&#xff1a; 格式是UTF-8…

基于Python制作一个动物识别小程序

目录 引言研究背景目的与意义 动物识别技术概述基本原理图像处理与特征提取机器学习与深度学习方法 数据集与数据预处理数据收集与构建数据预处理步骤数据增强技术 特征提取与选择基础特征提取方法特征选择与降维 引言 研究背景 动物识别是计算机视觉和模式识别领域的重要研究…

《深入浅出OCR》实战:基于CRNN的文字识别

✨专栏介绍: 经过几个月的精心筹备,本作者推出全新系列《深入浅出OCR》专栏,对标最全OCR教程,具体章节如导图所示,将分别从OCR技术发展、方向、概念、算法、论文、数据集等各种角度展开详细介绍。 💙个人主页: GoAI |💚 公众号: GoAI的学习小屋 | 💛交流群: 7049325…

在python中加载tensorflow-probability模块和numpy模块

目录 操作步骤&#xff1a; 注意&#xff1a; 问题&#xff1a; 解决办法&#xff1a; 操作步骤&#xff1a; 在虚拟环境的文件夹中&#xff0c;找到Scripts文件夹&#xff0c;点击进去&#xff0c;找到地址栏&#xff0c;在地址栏中输入cmd&#xff0c;进入如下界面。 输…

国产数据库兼容过程中涉及的MySQL非严格模式

点击上方蓝字关注我 在国产数据库兼容适配过程中&#xff0c;经常遇到因源数据库是MySQL&#xff0c;迁移至其他国产数据库后&#xff0c;因MySQL端兼容模式有非严格模式&#xff0c;导致适配过程过程中需要做调整。那么&#xff0c;MySQL主要的非严格模式小结如下&#xff1a;…