您的位置:首页 > 编程语言 > ASP

关于Java.lang.OutOfMemoryError:Metaspace错误分析思路

2016-08-27 14:47 471 查看
个人对于JVM内存模型不是很了解,只能一步步猜,一次次Google,以下为我的Google经历,有些曲折(主要还是因为自己不懂没经验):

第一步:

1. 看到【Java.lang.OutOfMemoryError:Metaspace】,我首先想到是Metaspace是什么东东,百度&Google后其实可以简单理解为为存放的类元信息和方法信息的一个JVM 块,Metaspace是JDK1.8的新特性吧,可以把它理解为JDK1.6的PermGen区域(但是也有很大区别的),Java官方对于此错误说明

Exception in thread thread_name: java.lang.OutOfMemoryError: Metaspace
Cause: Java class metadata (the virtual machines internal presentation of Java class) is allocated in native memory (referred to here as metaspace). If metaspace for class metadata is exhausted, a java.lang.OutOfMemoryError exception with a detail MetaSpace is thrown. The amount of metaspace that can be used for class metadata is limited by the parameter MaxMetaSpaceSize, which is specified on the command line. When the amount of native memory needed for a class metadata exceeds MaxMetaSpaceSize, a java.lang.OutOfMemoryError exception with a detail MetaSpace is thrown.
Action: If MaxMetaSpaceSize, has been set on the command-line, increase its value. MetaSpace is allocated from the same address spaces as the Java heap. Reducing the size of the Java heap will make more space available for MetaSpace. This is only a correct trade-off if there is an excess of free space in the Java heap. See the following action for Out of swap space detailed message.


以下为我搜索到一些链接:

          a) Java 8: 从永久代(PermGen)到元空间(Metaspace)http://blog.csdn.net/zhyhang/article/details/17246223  

第二步:

1. 听了老司机的建议:分析下内存溢出时候的heapdump,本身自己也不懂只能麻烦性能测试的同时给我dump一个包给我,现在问题来,我不会分析

,只能取找万年的百度的了,网上说MAT不错,那过段下载下来

2. 临时抱佛脚(发现自己学习新东西好费劲),找了个帖子参考他们一点点看看(先贴下帖子)

    a)   MAT - Memory Analyzer Tool 使用进阶 http://www.lightskystreet.com/2015/09/01/mat_usage/
3. 参考的来嘛,发现里头竟然有里头果然有两块区域使用很大(a:消息部分的; b: 缓存部分的),看来b是不像如果是a的问题的话那岂不是可以顺利甩锅了,心中偷乐..



4.  看来看去找来找去,发现这个区域只是占的空间比较大而已,也不见得把Metaspace怎么样啊,发现此路不通

5.  那就来看看是不是class加载重复了,那就看呗,看了下竟然很多class竟然生成了很多份,同时因为使用CGLIB的原因竟然一个Class生成了很多份,难道这地方有路?

第三步:想办法将服务器加载Class信息打印出来

1. 这个问题应该不难,万能的百度能搞不定么,JVM的verbose:class参数可以打印ClassLoader的信息的,将我的tomcat起来,发现日志信息只打印在控制台上,无从分析啊;

2. 那就将这部分内容打印到文件吧(我是这么想的),百度肯定有办法的,终于找到了只需要加个参数就好

catalina.bat

set JAVA_OPTS=-server -Xms2G -Xmx4G -Xss2m -XX:NewSize=1G -XX:MaxNewSize=1G -XX:MaxMetaspaceSize=512m -XX:SurvivorRatio=32 -verbose:gc -Xloggc:E:\V6.0\tomcat8\gc.log -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -noclassgc-verbose:class
-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -XX:TargetSurvivorRatio=50 -Dcom.sun.management.jmxremote -DA8.datasource.properies.filepath=%A8_HOME%/base/conf/datasourceCtp.properties -Dsun.rmi.dgc.client
ceb2
.gcInterval=21600000 -Dsun.rmi.dgc.server.gcInterval=21600000
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=java_pid.hprof -Djava.net.preferIPv4Stack=true -Dorg.apache.el.parser.COERCE_TO_ZERO=true -Djgroups.bind_addr=0.0.0.0 -DDEE_HOME=%A8_HOME%/base/dee/ -Djgroups.tcpping.initial_hosts=

if not "%JPDA%" == "" goto doJpda

if not "%SECURITY_POLICY_FILE%" == "" goto doSecurity

%_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%>>../logs/catalina.%Date:~0,10%.out

goto end

:doSecurity

%_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Djava.security.manager -Djava.security.policy=="%SECURITY_POLICY_FILE%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%"
-Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
>>../logs/catalina.%Date:~0,10%.out

goto end

:doJpda

if not "%SECURITY_POLICY_FILE%" == "" goto doSecurityJpda

%_EXECJAVA% %JAVA_OPTS% %JPDA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS%
%ACTION% >>../logs/catalina.%Date:~0,10%.out

goto end

:doSecurityJpda

%_EXECJAVA% %JAVA_OPTS% %JPDA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Djava.security.manager -Djava.security.policy=="%SECURITY_POLICY_FILE%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%"
-Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
>>../logs/catalina.%Date:~0,10%.out


goto end

startup.bat

set TITLE=SeeyonTomcat

rem start "%TITLE%" "%EXECUTABLE%" run %CMD_LINE_ARGS%
call "%EXECUTABLE%" run %CMD_LINE_ARGS%

参考链接:

a) 查看JVM类加载日志 http://k1121.iteye.com/blog/1279062

b) TOMCAT控制台日志输出到指定文件中 http://cuityang.iteye.com/blog/1884160

c) 监控Tomcat class日志加载 http://blog.sina.com.cn/s/blog_67e192090101foah.html

第三步:我要看看到底是再加载哪些类

1. 先上下我们性能测试环境跑到内存溢出的几段比较特殊的脚本吧

日志片段1:

from file:/D:/Seeyon/A8/ApacheJetspeed/webapps/seeyon/WEB-INF/lib/spring-core.jar]
[Loaded sun.reflect.GeneratedConstructorAccessor179 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor1980 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor1981 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor1982 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor1983 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor1984 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor1985 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor1986 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor1987 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor1988 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor1989 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor1990 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor1991 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor1992 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor1993 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor1994 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor1995 from __JVM_DefineClass__]
日志片段2:

[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getId_setId_long from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getName_setName_java_lang_String from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getName_setName_java_lang_String from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getId_setId_long from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getName_setName_java_lang_String from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getName_setName_java_lang_String from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getId_setId_long from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getName_setName_java_lang_String from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getName_setName_java_lang_String from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getId_setId_long from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getName_setName_java_lang_String from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getName_setName_java_lang_String from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getId_setId_long from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getName_setName_java_lang_String from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getName_setName_java_lang_String from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getId_setId_long from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getName_setName_java_lang_String from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getName_setName_java_lang_String from __JVM_DefineClass__]
[Loaded com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM_getId_setId_long from __JVM_DefineClass__]
2. 我首先看到的是第一段日志片段,[Loaded sun.reflect.GeneratedMethodAccessor1995 from __JVM_DefineClass__] 这是什么鬼?我就拿着__JVM_DefineClass__作为关键字到Google去查,果然有所斩获:
“一次不是非常愉快的jvm优化过程”这个帖子中这位哥们碰到的问题跟我的问题片段1很相似,在里面提到了由于大量使用反射就会导致这种场景,默认情况:小于15次时候会使用原生的反射,但是如果超过这个数的话JVM就会使用bytecode进行反射优化,从而对每个反射对象生成对应的动态类从而避免反射的低性能,我就按照楼主的说法在本地配置果然不再有片段1这的问题了

相关链接:

一次不是非常愉快的jvm优化过程http://hanzheng.github.io/tech/jvm/2013/10/25/last-time-with-jvm.html

sun.reflect.DelegatingClassLoader http://blog.sina.com.cn/s/blog_56832f710101jw4j.html
补充下:

-Dcom.sun.xml.bind.v2.bytecode.ClassTailor.noOptimize= true 此命令也可以表示直接关闭bytecode反射机制

3. 但是片段2中日志问题是什么原因呢?其中JaxbAccessor感觉很陌生,我就拿着"jaxbAccessor
permgen leak"作为关键字去查询(为什么用这个关键字,请意会)此处也是有发现的,先上链接吧:

Permgen Leak in JAXB due to recreation of JAXBContexts in CXF https://issues.apache.org/jira/browse/CXF-2939
由于系统平台使用的jersey2.21,而帖子说的是使用CXF,但是没问题的,都是Jaxb这就够了,我就在jersey源代码中找到了同样的类:org.glassfish.jersey.jaxb.internal.AbstractJaxbProvider,虽然是两个不同的框架,但是代码类及写法也是一样的,参考了下CXF-2939中将WeakHashMap改为HashMap(我想了下原因,不知道是否正确:JVM内存负荷比较大,GC一直在找目标,发现了weakReference直接干掉,但是GC只干掉了堆中的内容,但是metaspace没有干掉,就这么傻逼的一直加载就把内存搞爆了),马上改让性能测试测试下

相关链接:

JERSEY-2952 PermGen Leak:Each class reloaded multiple time https://java.net/jira/browse/JERSEY-2952(有点巧,但是一直就木修复)

4. 测试结果是跑了2个小时还是照样内存溢出,我把打击的(性能同事都有点看不上了,好菜),没办法继续看,这次把主要点集中到了片段2日志上来了

第四步:找到元凶com.seeyon.ctp.rest.resources.Pojo$JaxbAccessorM

1. 如前面贴的日志2中看到,一大堆这样的日志全都是针对Pojo这个类的,这个类有什么特殊的么?

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Pojo {
public Pojo(long id, String name) {
super();
this.id = id;
this.name = name;
}

public Pojo() {
super();
}

/*
* public Pojo(String json) { super(); }
*/
private long id;
private String name;

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}
2. 等待着等待着竟然老司机来了,说是不是Pojo有问题可能是@XmlRootElement这个注解搞的鬼,老司机都说了果断注释掉它,下来查了下XmlRootElement这个注解竟然用了jaxb是不是感觉非常开心,现在就是坐等性能测试同事开工测试啦

相关链接:

XmlRootElement JAXB注解 http://desert3.iteye.com/blog/1570092 总结:

1. 关于JVM这块没怎么认真看过(实际上是就没看过

),一下子都不知道如何下手,竟然还没测试妹子嫌弃,让我取好好看看JVM内存机制,不能忍啊!

2. 不管什么问题,不能就仅仅给我个表象(java.lang.OutOfMemoryError:Metaspace),必须要有详细的日志,如果没有日志想办法搞到日志!

3. Java 中反射还是要尽量少用,bytecode这个鬼他对于我而言还很陌生!

4. Jaxb我是不熟悉,以后有空熟悉下!

再补充下其他搜索的链接(个人觉得还是某些相关吧)

1. 深入解析String#intern http://tech.meituan.com/in_depth_understanding_string_intern.html
原因:我们使用的是FastJson,我在跟踪的时候发现页面第一次访问的时候加载了Serializer$类(具体日志弄丢了),就找到了它

2. Classloader leaks V – Common mistakes and Known offenders http://java.jiderhamn.se/2012/02/26/classloader-leaks-v-common-mistakes-and-known-offenders/
原因:这位大牛很瞧不上我使用Spring框架的,因为他觉得大量使用CGLIB生成一堆的反射对象(这是大牛考虑的,有点难为小的了),不过他讲的还是有些道理的,这货还写了个框架监控classloader-leak-prevention(https://github.com/mjiderhamn/classloader-leak-prevention)

------------------------------------华丽的分割线-----------------------------------------------------------------

1. 如果使用是老系统升级到JDK1.8的时候,由于jaxb已经是JDK的一部分,这个时候由于classpath中还有jaxb-impl.jar将引发jaxbContext无法被GC导致metaspace内存溢出,

如JDK-8146539:https://bugs.openjdk.java.net/browse/JDK-8146539
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: