某保险公司核心系统中间件Hibernate 一级缓存导致内存溢出的故障诊断

1.客户环境

  • Weblogic版本:9.2
  • Linux :suse 12
  • JDK版本: Sun JDK 1.5

2.故障现象

在 201X 年 X 月 X 日早上 10 点多时,某保险公司核心系统多个
Weblogic 的应用服务器都极度缓慢,正常的业务都无法开展。

3.详细分析

应用缓慢原因

检查 Weblogic 的相关日志文件,发现有内存溢出的情况发生,如下:
java.lang.OutOfMemoryError: Java heap space

并产生了相关的内存溢出 HeapDump 文件:java_pid25528.hprof 随后对该文件进行下一步分析,可知:

hibernate_cache_outOfmemory1

其中占据内存较多的两组对象都是产生了 Stuck 的 Weblogic 线程,分别占用的内存是 84.85%和 10.40%,这两部分加起来是 95.25%,

说明已经占用了整个 Weblogic Server 的绝大部分内存。

进一步分析内存溢出对象

进一步分析,发现大量的对象都被缓存在
(org.hibernate.engine.StatefulPersistenceContext)中.
hibernate_cache_outofMemory_statefulPeristence

在这个缓存中的 对 象 是我们的业务对象

hibernate_cache_outofMemory_entityName

业务对象如下:

1
2
com.**.scms.inf.model.ScmsCcommission
com.**.scms.inf.model.ScmsCcomissionId

由于这些缓存的数据无法释放,系统在业务量较大时,内存迅速 在 2 分钟内从 1G 增长到 4G,并且不断地进行 Full GC,导致系统极其缓慢。

StatefulPersistenceContext 解密

SessionImpl

Hibernate的一级缓存就是指Session缓存。通过查看Session接口的实现类——SessionImpl.java的源码可发现有如下两个类:

1
2
161   	private transient ActionQueue actionQueue;
162 private transient StatefulPersistenceContext persistenceContext;

actionQueue它是一个行动队列,它主要记录crud操作的相关信息。
persistenceContext它是持久化上下文,它其实才是真正的缓存。

persistenceContext缓存存储方式

当执行完以下这句代码:

1
Customer customer = session.get(Customer.class, 1);

就会向一级缓存中存储数据,一级缓存其底层使用了一个Map集合来存储,Map的key存储的是一级缓存对象,而value存储的是快照。通过在这句代码上打个断点,然后以debug的方式运行,Watch一下session会看得更加清楚,如下:

persistenceContext-debug1

persistenceContext-debug2

Hibernate session的清理方法

从如下的代码中,也可以看出,session在执行相关清理工作时,也会执行persistenceContext.clear();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void clear() {
281 errorIfClosed();
282 checkTransactionSynchStatus();
283 persistenceContext.clear();
284 actionQueue.clear();
285 }


466 /**
467 * clear all the internal collections, just
468 * to help the garbage collector, does not
469 * clear anything that is needed during the
470 * afterTransactionCompletion() phase
471 */
472 private void cleanup() {
473 persistenceContext.clear();
474 }

StatefulPersistenceContext源码

StatefulPersistenceContext源码

org.hibernate.engine.StatefulPersistenceContext
结合以上代码和该类的实现类可以确定是一个缓存上下文引用,而且从session.cleanup()方法session.cleanup()实际调用的就是persistenceContext.clear(),注意这句话clear all the internal collections, just to help the garbage collector;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
58 import org.hibernate.util.IdentityMap;
59 import org.hibernate.util.MarkerObject;

62 * A <tt>PersistenceContext</tt> represents the state of persistent "stuff" which
63 * Hibernate is tracking. This includes persistent entities, collections,
64 * as well as proxies generated.
65 * </p>
66 * There is meant to be a one-to-one correspondence between a SessionImpl and
67 * a PersistentContext. The SessionImpl uses the PersistentContext to track
68 * the current state of its context. Event-listeners then use the
69 * PersistentContext to drive their processing.
70 *
71 * @author Steve Ebersole
72 */

4.建议

由于 Hibernate 的一级缓存是其内部使用的,无法关闭或停用(随着Session 销毁)。从
Hibernate 的手册或文档中可知,Hibernate 的一级缓存的清除可通过以下方式:
1)对于单个对象的清除:

Session session=sessionFactory.getCurrentSession(); session.evict(entity);

2)对于实体集合的清除:

Session session=sessionFactory.getCurrentSession(); session.clear();
建议在程序中加入对 Hibernate 一级缓存的清除工作,以便可以其
内存数据可以及时释放。

关于Hibernate缓存问题可参考:
Hibernate缓存策略

5.可能场景

如果应用会定时启动几个quartz任务来处理复杂且影响页面、响应时间的业务,这部分业务的业务数据是从数据库查的,只有业务数据全都被处理完后这个quartz才会结束。
当这几个 quartz任务的业务数据较多的时候,就会有很多对象被填入一级缓存这样一来持久化上下文中保存的对象越来越多。最终导致OOM.

6.参考

http://www.docjar.com/html/api/org/hibernate/engine/StatefulPersistenceContext.java.html

http://www.docjar.com/html/api/org/hibernate/impl/SessionImpl.java.html

https://blog.csdn.net/yerenyuan_pku/article/details/70148567

https://www.cnblogs.com/hyl8218/p/5076338.html

7.了解Hibernate缓存策略请看

Hibernate缓存策略
Hibernate缓存策略

8.欢迎关注米宝窝,持续更新中,谢谢!

米宝窝 https://rocklei123.github.io/

-------------本文结束感谢您的阅读-------------
欢迎持续关注米宝窝,定期更新谢谢! https://rocklei123.github.io/
欢迎持续关注我的CSDN https://blog.csdn.net/rocklei123
rocklei123的技术点滴
熬夜写博客挺辛苦的,生怕猝死,所以每当写博客都带着听诊器,心脏一有异响,随时按Ctrl+S。
rocklei123 微信支付

微信支付

rocklei123 支付宝

支付宝