情景 项目上线了一个接口,先灰度一台机器观察调用情况; 接口不断的调用,过了一段时间,发现机器上的接口调用开始报OOM异常 ! 当天就是上线deadline了,刺激。。 发现问题 第一步,使用j…
##### 情景
项目上线了一个接口,先灰度一台机器观察调用情况; 接口不断的调用,过了一段时间,发现机器上的接口调用开始报 ```java
OOM异常
##### 发现问题
###### 第一步,使用 ```java
jps
``` 命令获取出问题jvm进程的进程ID
使用 ```java
jps -l -m
``` 获取到当前jvm进程的pid,通过上述命令获取到了服务的进程号:427726 (此处假设为这个) ') jps命令
###### 第二步,使用 ```java
jstat
``` 观察jvm状态,发现问题
我们使用 ```java
jstat -gc pid time
``` 命令观察GC,发现GC在YGC后,GC掉的内存并不多,每次YGC后都有一部分内存未回收,导致在多次YGC后回收不掉的内存被挪到堆的old区,old满了之后FGC发现也是回收不掉; 这里基本可以确定是内存泄漏的问题了,下面我们有简单看了下机器的cpu、内存、磁盘状态
jstat命令:
在这里先简单说一下,堆的GC:
###### 第三步,观察机器状态,确认问题
使用 ```java
top -p pid
``` 获取进程的cpu和内存使用率;查看RES 和 %CPU %MEM三个指标: ')
在这里先简单说一下,top命令展示的内容:
发现机器的自身状态不存在问题, so毋庸置疑,发现问题了,典型的内存泄漏。。
###### 第四步,使用jmap获取jvm进程dump文件
我们使用 ```java
jmap -dump:format=b,file=dump_file_name pid
``` 命令,将当前机器的jvm的状态dump下来或缺的一份dump文件,用做下面的分析
jmap命令:
接下来,回滚灰度的机器,开始解决问题=.=
##### 解决问题
###### 第一步,dump文件分析
在这里,我们分析dump文件,使用的 ```java
Jprofiler
``` 软件,就是下面这个东东: ')
具体的使用方法,在这就不再赘述了,下面将dump文件导入到 ```java
Jprofiler
``` 中: 选择 ```java
Heap Walker
``` 中的 ```java
Current Object Set
``` ,这里面显示的是当前的类的占用资源,从占用空间从大到小排序; ') 从上图中,没有观察出什么问题,我们点击 ```java
Biggest Objects
``` ,查看哪个对象的占用的内存高: ') 从上图中,我们发现 ```java
org.janusgraph.graphdb.database.StandardJanusGraph
``` 这个对象居然占用了高达724M的内存! 看来内存泄漏八九不离十就是这个对象的问题了! 再点开看看 ,如下图,可以发现是一个 ```java
openTransactions
``` 的类型为 ```java
ConcurrentHashMap
``` 的数据结构: ')
###### 第二步,源码查找定位代码
这到底是什么对象呢,去项目中查找一下,打开idea-打开项目-双击shift键-打开全局类查找-输入 ```java
StandardJanusGraph
``` ,如下图: ') 发现是我们项目使用的图数据库 ```java
janusgraph
``` 的一个类,找到对应的数据结构: 类型定义:
```java
private Set<StandardJanusGraphTx> openTransactions;
初始化为一个ConcurrentHashMap:
openTransactions = Collections.newSetFromMap(new
ConcurrentHashMap<StandardJanusGraphTx, Boolean>(100,
0.75f, 1));
观察上述代码,我们可以看到,里面的存储的 ```java
StandardJanusGraphTx
```java
// 找到执行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方法
}
在我们对图数据库中图数据操作的过程中,采用的是手动创建事务的方式,在每次查询图数据库之前,我们都会调用类似于 java dataDao.begin()
代码, 其中就是调用的 ```java
public JanusGraphTransaction newTransaction()
最后,我们简单的看下源码可以发现,从上述内存泄漏的map中去除数据的逻辑就是 ```java
commit
``` 事务的接口,调用链如下:
```java
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();
}
}
}
终于,我们找到了内存泄漏的根源所在:项目代码中存在调用了事务 java begin
但是没有 ```java
commit
###### 第三步,修复问题验证
解决问题: 找到内存泄漏接口的代码,并发现了没有commit()的位置,try-catch-finally中添加上了commit()代码;
提交-部署-发布-灰度一台机器后观察内存泄漏的现象消失,GC回收正常;
内存泄漏问题解决,项目如期上线~
##### 最后
大家,有没有遇到过内存泄漏的情况,欢迎在评论区说出你的故事=.=
写这篇文章耗费的时间超出了我的预料,预计2个小时写完,结果花了一下午的时间...
也欢迎大家关注我的 ```java
oschina
``` 和微信搜索公众号[ ```java
匠心Java
``` ]支持一下作者,作者定期分享工作中的所见所得~ ')
本文标题: 事务处理不当,线上接口又双叒内存泄漏了!(附图解问���全过程)
本文作者: OSChina
发布时间: 2021年04月15日 09:17
最后更新: 2025年04月03日 11:07
原始链接: https://haoxiang.eu.org/a5721e3d/
版权声明: 本文著作权归作者所有,均采用CC BY-NC-SA 4.0许可协议,转载请注明出处!