温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

记一次OOM排查解决

发布时间:2020-06-15 22:37:02 来源:网络 阅读:9913 作者:zhuwensheng 栏目:开发技术

现场人员反馈tomcat假死,已不能访问,而且一直报如下异常:

SEVERE:Memory usage is low, parachute is non existent, your system may start failing.
java.lang.OutOfMemoryError: Java heap space

很显然堆内存溢出了,现场配置了2G的堆内存,用jmap命令dump heap失败,添加参数-F强制dump半天没有反应,只能带着-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tomcat/logs参数重启,过了半天问题又出现了,这次得到了堆内存的快照,用Memory Analyzer (MAT)分析,概要信息如下:

One instance of "org.hibernate.impl.SessionFactoryImpl" loaded by "org.apache.catalina.loader.WebappClassLoader @ 0x788c95390" occupies 1,567,956,008 (80.22%) bytes. The memory is accumulated in one instance of "java.util.concurrent.ConcurrentHashMap$Segment[]" loaded by "<system class loader>".

Keywords
org.hibernate.impl.SessionFactoryImpl
org.apache.catalina.loader.WebappClassLoader @ 0x788c95390
java.util.concurrent.ConcurrentHashMap$Segment[]

从这个概要信息可以看到这个OOM是和Hibernate有关的。

视图Biggest Objects by Retained Size如下:

记一次OOM排查解决

可以看到对象实例org.hibernate.impl.SessionFactoryImpl @ 0x78ada8e98的Retained Size竟然达到了1.5GB,也就是说如果此对象实例被GC回收将会有1.5GB的堆内存释放。

Dominator Tree如下:

记一次OOM排查解决


从此图可以看到org.hibernate.stat.ConcurrentStatisticsImpl @ 0x78b1f2378实例持有了大量的ConcurrentHashMap对象,占到了整个堆内存的80%,导致了内存泄漏。

视图Shortest Paths To the Accumulation Point如下:

记一次OOM排查解决

从上图可以清晰的看出类org.hibernate.impl.SessionFactoryImpl、org.hibernate.stat.ConcurrentStatisticsImpl以及java.util.concurrent.ConcurrentHashMap的相互依赖关系。对象实例org.hibernate.impl.SessionFactoryImpl @ 0x78ada8e98的属性statistics占用了大量的内存,而此属性类型为org.hibernate.stat.ConcurrentStatisticsImpl,在类org.hibernate.impl.SessionFactoryImpl(183行)中的定义如下:

private final transient Statistics statistics ;

此属性在构造函数中实例化(204行):

this.statistics = new ConcurrentStatisticsImpl( this );

进一步观察类org.hibernate.stat.ConcurrentStatisticsImpl发现此类持有一个类型为 java.util.concurrent.ConcurrentHashMap的queryStatistics属性(103行),定义如下:

/**
 * entity statistics per query string (HQL or SQL)
 */
private final ConcurrentMap queryStatistics = new ConcurrentHashMap();

类org.hibernate.stat.ConcurrentStatisticsImpl中有一个getQueryStatistics(String queryString)方法负责对此ConcurrentHashMap进行操作,源码如下:

public QueryStatistics getQueryStatistics(String queryString) {
    ConcurrentQueryStatistics qs = (ConcurrentQueryStatisticsImpl) queryStatistics.get( queryString );
    if ( qs == null) {
        qs = new ConcurrentQueryStatisticsImpl( queryString );
        ConcurrentQueryStatisticsImpl previous;
        if ( (previous = (ConcurrentQueryStatisticsImpl) queryStatistics.putIfAbsent( queryString ) != null) {
            qs = previous;
        }
    }
    return qs;
}

也就是说Hibernate每次执行一个SQL或HQL都会都会将这个SQL或HQL作为key、new一个ConcurrentQueryStatisticsImpl对象实例作为value放入ConcurrentHashMap中。但是此方法是否调用受类org.hibernate.stat.ConcurrentStatisticsImpl的布尔属性isStatisticsEnabled控制,此属性值通过类org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean的属性hibernateProperties配置,名称为hibernate.generate_statistics。经查此项配置和Hibernate的SQL、HQL统计有关,这个配置项开启Hibernate就会统计执行的SQL和HQL的执行信息,如果有大量请求访问,Hibernate就会缓存大量的SQL从而导致内存溢出,将此参数设置为false问题解决。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI