情景

项目上线了一个接口,先灰度一台机器观察调用情况;

接口不断的调用,过了一段时间,发现机器上的接口调用开始报 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 -class pid time : -class 监控jvm的类加载信息,pid 监控的jvm进程id,time每隔多少毫秒刷新一次

在这里先简单说一下,堆的GC:

年龄达到一定值(年龄阈值,可以通过-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 中:

选择 Heap Walker 中的 Current Object Set ,这里面显示的是当前的类的占用资源,从占用空间从大到小排序;

从上图中,没有观察出什么问题,我们点击 Biggest Objects ,查看哪个对象的占用的内存高:

从上图中,我们发现 org.janusgraph.graphdb.database.StandardJanusGraph 这个对象居然占用了高达 724M 的内存! 看来内存泄漏八九不离十就是这个对象的问题了!

再点开看看 ,如下图,可以发现是一个 openTransactions 的类型为 ConcurrentHashMap 的数据结构:

第二步,源码查找定位代码

这到底是什么对象呢,去项目中查找一下,打开idea-打开项目-双击shift键-打开全局类查找-输入 StandardJanusGraph ,如下图:

发现是我们项目使用的图数据库 janusgraph 的一个类,找到对应的数据结构:

类型定义:

private Set openTransactions;

初始化为一个ConcurrentHashMap:

openTransactions = Collections.newSetFromMap(new ConcurrentHashMap(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对象进行了add            return 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() 代码,

其中就是调用的 public JanusGraphTransaction newTransaction() 这个方法;

最后,我们简单的看下源码可以发现,从上述内存泄漏的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();  // // 调用releaseTransaction            if (null != config.getGroupName() && !success) {                MetricManager.INSTANCE.getCounter(config.getGroupName(), "tx", "commit.exceptions").inc();            }        }    }

终于,我们找到了内存泄漏的根源所在:项目代码中存在调用了事务 begin 但是没有 commit的代码!

第三步,修复问题验证

解决问题: 找到内存泄漏接口的代码,并发现了没有commit()的位置,try-catch-finally中添加上了commit()代码;

提交-部署-发布-灰度一台机器后观察内存泄漏的现象消失,GC回收正常;

内存泄漏问题解决,项目如期上线~

最后

大家,有没有遇到过内存泄漏的情况,欢迎在评论区说出你的故事=.=

查找这个接口的调用_事务处理不当,线上接口又双叒内存泄漏了!(附图解问题全过程)...相关推荐

  1. 事务处理不当,线上接口又双叒内存泄漏了!(附图解问题全过程)

    来自:匠心Java 情景 项目上线了一个接口,先灰度一台机器观察调用情况:接口不断的调用,过了一段时间,发现机器上的接口调用开始报 OOM异常 ! 当天就是上线deadline了,刺激.. 发现问题 ...

  2. Java线上接口耗时分析神器 Arthas

    文章目录 背景 问题 Arthas github地址 官网文档地址: 说明 安装 运行Arthas trace命令的使用 统计时间不准确问题 总结 背景 有时候我们在对线上接口作性能优化的时候需要找出 ...

  3. php 动态彩码辨色 接口的调用_好用的云函数!后端低代码接口开发,零基础编写API接口...

    前言 在开发项目过程中,经常需要用到API接口,实现对数据库的CURD等操作. 不管你是专业的PHP开发工程师,还是客户端开发工程师,或者是不懂编程但懂得数据库SQL查询,又或者是完全不太懂技术的人, ...

  4. 支付宝支付之“单笔转账到支付宝账户接口”的调用(生成签名、上传应用公钥、下载SDK、接口调用、报错自动排查、查看错误码)

    支付宝接口调用 "单笔转账到支付宝账户"的接口调用,一般涉及到下面几个知识点 1.生成签名 在使用支付宝接口的时候,需要使用支付宝的签名,这里需要使用支付宝的RSA生成工具. 关于 ...

  5. java 接口和虚构_深入理解Java的接口和抽象类

    深入理解Java的接口和抽象类 对于面向对象编程来说,抽象是它的一大特征之一.在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类.这两者有太多相似的地方,又有太多不同的地方.很多人在初学的 ...

  6. python生成接口文档_使用apiDoc实现python接口文档编写

    使用apiDoc实现python接口文档编写 apiDoc的安装 npm install apidoc -g 生成api的终端命令:apidoc -i 代码所在路径-o 生成文件的路径 接口文档的编写 ...

  7. java批量删除接口怎么定义_教你在Java接口中定义方法

    基本上所有的Java教程都会告诉我们Java接口的方法都是public.abstract类型的,没有方法体的. 但是在JDK8里面,你是可以突破这个界限的哦. 假设我们现在有一个接口:TimeClie ...

  8. 分页缓冲池占用很高怎么解决_一次线上服务高 CPU 占用优化实践

    线上有一个非常繁忙的服务的 JVM 进程 CPU 经常跑到 100% 以上,下面写了一下排查的过程.通过阅读这篇文章你会了解到下面这些知识. Java 程序 CPU 占用高的排查思路 可能造成线上服务 ...

  9. 内存泄漏java例子_一次线上Java应用内存泄漏分析实例

    由于JVM的内存管理采用GC垃圾自动回收机制,这使得Java程序员在编程的时候确实可以从内存管理中释放出来,但这也引发了另外一个大问题,一旦Java应用出现内存泄漏的时候,常常让人措手不及,陷入无从下 ...

最新文章

  1. MySql 几个命令
  2. 洗牌算法shuffle
  3. 真正意义的Anchor-Free,FCOS目标检测算法了解一下
  4. gmat阅读.html,GMAT阅读长难句50句+参考译文.pdf
  5. Git--版本管理的使用及理解
  6. C语言学习笔记---位字段
  7. CentOS7安装wxWidgets错误解决
  8. Android应用开发之版本更新你莫愁
  9. python以写模式打开录入_Python基础06
  10. Win10安装乌班图18双系统
  11. STM32CubeIDE 使用技巧和说明
  12. 制作后台管理系统首页
  13. html2pdf使用总结
  14. 死链对网站的影响、产生过程、解决办法汇总
  15. AHU校赛网赛解题报告
  16. U盘重装系统win7_U盘安装win7教程
  17. java.util.zip 类 ZipEntry
  18. 坚持写博客以来的感受和改变
  19. Mac-XQuartz-linux
  20. 预测控制(二):NMPC轨迹跟踪

热门文章

  1. [Python图像处理] 三十五.OpenCV图像处理入门、算数逻辑运算与图像融合(推荐)
  2. MFC 基础知识:对话框背景添加图片和按钮Button添加图片
  3. 【数据结构与算法】之深入解析“最长公共前缀”的求解思路与算法示例
  4. iOS之深入解析渲染的底层原理
  5. 30秒实现Vue吸顶效果
  6. 大数据WEB阶段(九)Myeclipse中配置Tomcat并发布项目
  7. 网口扫盲一:网卡初步认识
  8. [Qt教程] 第37篇 网络(七)TCP(一)
  9. html5 video如何添加进度条_教你制作独一无二的进度条视频效果
  10. web前端学习文档 电子版_web前端小白系统入门学习