事务处理不当,线上接口又双叒内存泄漏了!(附图解问题全过程)
来自:匠心Java
情景
项目上线了一个接口,先灰度一台机器观察调用情况;接口不断的调用,过了一段时间,发现机器上的接口调用开始报 OOM异常
!
当天就是上线deadline了,刺激。。
发现问题
第一步,使用 jps
命令获取出问题jvm进程的进程ID
使用 jps-l-m
获取到当前jvm进程的pid,通过上述命令获取到了服务的进程号:427726 (此处假设为这个)jps命令
jps
(JVM Process Status Tool):显示指定系统内所有的HotSpot虚拟机进程
jps-l-m
:参数-l列出机器上所有jvm进程,-m显示出JVM启动时传递给main()的参数
第二步,使用 jstat
观察jvm状态,发现问题
因为是OOM异常,所以我们首先重启机器观察了JVM的运行情况;
我们使用 jstat-gc pid time
命令观察GC,发现GC在YGC后,GC掉的内存并不多,每次YGC后都有一部分内存未回收,导致在多次YGC后回收不掉的内存被挪到堆的old区,old满了之后FGC发现也是回收不掉;
这里基本可以确定是内存泄漏的问题了,下面我们有简单看了下机器的cpu、内存、磁盘状态
jstat命令:
jstat
(JVM statistics Monitoring)是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
jstat-gc pid time
:-gc 监控jvm的gc信息,pid 监控的jvm进程id,time每个多少毫秒刷新一次
jstat-gccause pid time
:-gccause 监控gc信息并显示上次gc原因,pid 监控的jvm进程id,time每个多少毫秒刷新一次
jstat-classpid time
:-class 监控jvm的类加载信息,pid 监控的jvm进程id,time每个多少毫秒刷新一次
在这里先简单说一下,堆的GC:
在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。
年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的,minor GC会一直重复这样的过程。
第三步,观察机器状态,确认问题
使用 top-p pid
获取进程的cpu和内存使用率;查看RES 和 %CPU %MEM三个指标:
在这里先简单说一下,top命令展示的内容:
VIRT:virtual memory usage 虚拟内存 1、进程“需要的”虚拟内存大小,包括进程使用的库、代码、数据等 2、假如进程申请100m的内存,但实际只使用了10m,那么它会增长100m,而不是实际的使用量
RES:resident memory usage 常驻内存 1、进程当前使用的内存大小,但不包括swap out 2、包含其他进程的共享 3、如果申请100m的内存,实际使用10m,它只增长10m,与VIRT相反 4、关于库占用内存的情况,它只统计加载的库文件所占内存大小
SHR:shared memory 共享内存 1、除了自身进程的共享内存,也包括其他进程的共享内存 2、虽然进程只使用了几个共享库的函数,但它包含了整个共享库的大小 3、计算某个进程所占的物理内存大小公式:RES – SHR 4、swap out后,它将会降下来
DATA 1、数据占用的内存。如果top没有显示,按f键可以显示出来。2、真正的该程序要求的数据空间,是真正在运行中要使用的。
ps : 如果程序占用实存比较多,说明程序申请内存多,实际使用的空间也多。如果程序占用虚存比较多,说明程序申请来很多空间,但是没有使用。
发现机器的自身状态不存在问题, so毋庸置疑,发现问题了,典型的内存泄漏。。
第四步,使用jmap获取jvm进程dump文件
我们使用 jmap-dump:format=b,file=dump_file_name pid
命令,将当前机器的jvm的状态dump下来或缺的一份dump文件,用做下面的分析
jmap命令:
jmap
(JVM Memory Map)命令用于生成heap dump文件,还可以查询finalize执行队列、Java堆和永久代的详细信息,如当前使用率、当前使用的是哪种收集器等。
jmap-dump:format=b,file=dump_file_name pid
:file=指定输出数据文件名, pid jvm进程号
接下来,回滚灰度的机器,开始解决问题=.=
解决问题
第一步,dump文件分析
在这里,我们分析dump文件,使用的 Jprofiler
软件,就是下面这个东东:
具体的使用方法,在这就不再赘述了,下面将dump文件导入到 Jprofiler
中:选择 HeapWalker
中的 CurrentObjectSet
,这里面显示的是当前的类的占用资源,从占用空间从大到小排序;从上图中,没有观察出什么问题,我们点击 BiggestObjects
,查看哪个对象的占用的内存高:从上图中,我们发现 org.janusgraph.graphdb.database.StandardJanusGraph
这个对象居然占用了高达724M的内存!
看来内存泄漏八九不离十就是这个对象的问题了!再点开看看 ,如下图,可以发现是一个 openTransactions
的类型为 ConcurrentHashMap
的数据结构:
第二步,源码查找定位代码
这到底是什么对象呢,去项目中查找一下,打开idea-打开项目-双击shift键-打开全局类查找-输入 StandardJanusGraph
,如下图:发现是我们项目使用的图数据库 janusgraph
的一个类,找到对应的数据结构:类型定义:
private Set<StandardJanusGraphTx> openTransactions;
初始化为一个ConcurrentHashMap:
openTransactions = Collections.newSetFromMap(new
ConcurrentHashMap<StandardJanusGraphTx, Boolean>(100,
0.75f, 1));
观察上述代码,我们可以看到,里面的存储的 StandardJanusGraphTx
从字面意义上理解是janusgraph框架中的事务对象,下面往上追一下代码,看看什么时候会往这个Map中赋值:
// 找到执行openTransactions.add()的方法public StandardJanusGraphTx newTransaction(final TransactionConfiguration configuration) {if (!isOpen) ExceptionFactory.graphShutdown();try {StandardJanusGraphTx tx = new StandardJanusGraphTx(this, configuration);tx.setBackendTransaction(openBackendTransaction(tx));openTransactions.add(tx); // 注意!此处对上述的map对象进行了addreturn tx;} catch (BackendException e) {throw new JanusGraphException("Could not start new transaction", e);}}// 上述发现,是一个newTransaction,创建事务的一个方法,为确保起见,再往上跟找到调用上述方法的类:public JanusGraphTransaction start() {TransactionConfiguration immutable = new ImmutableTxCfg(isReadOnly, hasEnabledBatchLoading,assignIDsImmediately, preloadedData, forceIndexUsage, verifyExternalVertexExistence,verifyInternalVertexExistence, acquireLocks, verifyUniqueness,propertyPrefetching, singleThreaded, threadBound, getTimestampProvider(), userCommitTime,indexCacheWeight, getVertexCacheSize(), getDirtyVertexSize(),logIdentifier, restrictedPartitions, groupName,defaultSchemaMaker, customOptions);return graph.newTransaction(immutable); // 注意!此处调用了上述的newTransaction方法}// 接着找上层调用,发现了最上层的方法public JanusGraphTransaction newTransaction() {return buildTransaction().start(); // 此处调用了上述的start方法}
在我们对图数据库中图数据操作的过程中,采用的是手动创建事务的方式,在每次查询图数据库之前,我们都会调用类似于 dataDao.begin()
代码, 其中就是调用的 publicJanusGraphTransactionnewTransaction()
这个方法;
最后,我们简单的看下源码可以发现,从上述内存泄漏的map中去除数据的逻辑就是 commit
事务的接口,调用链如下:
public void closeTransaction(StandardJanusGraphTx tx) {openTransactions.remove(tx); // 从map中删除StandardJanusGraphTx对象}private void releaseTransaction() {isOpen = false;graph.closeTransaction(this); // 调用上述closeTransaction方法vertexCache.close();}public synchronized void commit() {Preconditions.checkArgument(isOpen(), "The transaction has already been closed");boolean success = false;if (null != config.getGroupName()) {MetricManager.INSTANCE.getCounter(config.getGroupName(), "tx", "commit").inc();}try {if (hasModifications()) {graph.commit(addedRelations.getAll(), deletedRelations.values(), this);} else {txHandle.commit(); // 这个commit方法中释放事务也是调用releaseTransaction}success = true;} catch (Exception e) {try {txHandle.rollback();} catch (BackendException e1) {throw new JanusGraphException("Could not rollback after a failed commit", e);}throw new JanusGraphException("Could not commit transaction due to exception during persistence", e);} finally {releaseTransaction(); // // 调用releaseTransactionif (null != config.getGroupName() && !success) {MetricManager.INSTANCE.getCounter(config.getGroupName(), "tx", "commit.exceptions").inc();}}}
终于,我们找到了内存泄漏的根源所在:项目代码中存在调用了事务 begin
但是没有 commit
的代码!
第三步,修复问题验证
解决问题:找到内存泄漏接口的代码,并发现了没有commit()的位置,try-catch-finally中添加上了commit()代码;
提交-部署-发布-灰度一台机器后观察内存泄漏的现象消失,GC回收正常;
内存泄漏问题解决,项目如期上线~
最后
大家,有没有遇到过内存泄漏的情况,欢迎在评论区说出你的故事=.=
写这篇文章耗费的时间超出了我的预料,预计2个小时写完,结果花了一下午的时间...
原创不易,如果大家有所收获,希望大家可以在看支持一下~
特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:长按订阅更多精彩▼如有收获,点个在看,诚挚感谢
事务处理不当,线上接口又双叒内存泄漏了!(附图解问题全过程)相关推荐
- 查找这个接口的调用_事务处理不当,线上接口又双叒内存泄漏了!(附图解问题全过程)...
情景 项目上线了一个接口,先灰度一台机器观察调用情况: 接口不断的调用,过了一段时间,发现机器上的接口调用开始报 OOM异常 ! 当天就是上线deadline了,刺激.. 发现问题 第一步,使用 jp ...
- Java线上接口耗时分析神器 Arthas
文章目录 背景 问题 Arthas github地址 官网文档地址: 说明 安装 运行Arthas trace命令的使用 统计时间不准确问题 总结 背景 有时候我们在对线上接口作性能优化的时候需要找出 ...
- 搭建线上教学平台双师教育必不可少
在近几年,与在线教育一样,"双师教育"也成为了一个行业的热门词,双师课堂因此也受到许多教育机构的青睐,像是一些知名机构也都纷纷推出双师教学,那么为什么大家会说搭建线上教学平台&qu ...
- 【工具】目前几种常见的线上接口文档管理平台的比较
文章目录 一.前言 二.接口文档在线平台 1.apizza 1.1 文档导出html 1.2 导入Postman.json和Swagger.json文件 2. YApi 2.1 源码开源&免费 ...
- 【蓝桥真题7】贴吧车队作弊?应对线上考和双填趋势,我们该如何备考?
⭐️引言⭐️ 大家好,我是执梗.还有一个星期蓝桥杯就要开赛了,但是现在却风起云涌,由于疫情的原因,绝大多数地区已经改为线上考试.理所当然,在这种趋势下,出现作弊行为肯定是无法避免的.甚至在贴吧都沦陷成 ...
- SSM基于java的线上阅读平台的设计与实现 毕业设计-附源码291023
SSM线上阅读平台开发 摘 要 随着互联网趋势的到来,各行各业都在考虑利用互联网将自己推广出去,最好方式就是建立自己的互联网系统,并对其进行维护和管理.在现实运用中,应用软件的工作规则和开发步骤,采用 ...
- c iostream.源码_通达信《K线上画趋势线预警》精选指标(附源码)
通达信<K线上画趋势线预警>精选指标 K线上画趋势线预警源码: N:=5; MA5:=EMA(C,5)COLORWHITE; MA13:=EMA(C,13)COLORCYAN; MA21: ...
- 【干货】奢侈品线上消费圈层洞察-阿里妈妈.pdf(附下载链接)
大家好,我是文文(微信号:sscbg2020),今天给大家分享阿里妈妈于2021年2月份发布的<奢侈品线上消费圈层洞察.pdf>,关注奢侈品的伙伴们别错过了. 本报告共31页,包含如下三大 ...
- 分享一次解决线上java应用导致JVM内存溢出(OOM)的问题
某个线上的应用运行几天后,总是出现卡死甚至出现OOM的情况. 注:文中图片可能与描述不符,仅作为演示! 通过Linux的top命令查看cpu占比 首先通过top命令查看,发现某个java程序占用了较高 ...
最新文章
- 自建WIN10 FTP无法访问的解决方法
- [Unity WWW] 跨域访问解决方法
- docker 内存 cpu 限制 简介
- 从RocketMQ看长轮询(Long Polling)
- 使用ABAP代码获得tcode RZ11里的参数值
- Java:数组排序输出
- 在Git中找到破坏测试的提交
- js实现搜索框智能提示上下移动效果
- 知易游戏开发教程cocos2d-x移植版
- 图像处理五:python读取图片的几种方式
- 59.Linux/Unix 系统编程手册(下) -- SOCKET: Internet Domain
- windows环境下vue开发环境搭建
- printf 输出格式
- ElementUI修改Dialog的标题样式
- 线元法输入曲线要素_交点法、线元法
- 【11-13】A股主要指数的市盈率(PE)估值高度
- java table 增加行_使用POI给word中的表格增加行
- 一个短信息运营商SP告诉你手机短信收费黑幕!!
- python断点续传下载_Python 3 爬虫|第12章:并发下载大文件 支持断点续传
- 如何打造极速F1赛事?乐视云用六路信号还原比赛现场
热门文章
- java运行在用户态_理解Linux用户态和内核态
- 图论刷水题记录(二)(最短路-----SPFA算法)
- 思科服务器查看生成树协议,CISCO中生成树协议的配置
- powerbi输入数据_Power BI 的多种共享方式
- 【学习笔记】超简单的多项式牛顿迭代(含泰勒展开式、牛顿迭代全套证明)
- 在html中加入评论,在自己网页中引入Valine评论
- sqoop mysql parquet_sqoop一些语法的使用
- mysql 存储guid_我应该如何在MySQL表中存储GUID?
- pymysq向mysql写数据 为什么本地无法查看_从运维角度浅谈MySQL数据库优化,中小企业DBA必会...
- gradle项目 避免每次下载gradle文件/解决依赖下载慢的问题