`
TimerBin
  • 浏览: 354856 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java heap space 问题查找

    博客分类:
  • JAVA
 
阅读更多


 在项目开发上线的过程中,最近发现一个Dubbo服务隔7天左右就会出现以下问题:

Exception in thread "Timer-0" java.lang.OutOfMemoryError: Java heap space

 

(一开始使用findBugs进行扫描,并未扫描出可用结果)

首先,介绍一个免费开源分析dump的软件Memory Analyzer,下载地址如下所示:(同事介绍)

http://www.eclipse.org/mat/downloads.php
此工具需要依赖于dump,可以根据以下命令生成JVM的dump文件

ps aux | grep xxx 查询进程ID

jmap -dump:live,format=b,file=文件名.bin   进程ID

 

可将生成的dump文件下载到本地,使用Memory Analyzer打开其文件进行分析。

最好在进程刚刚开启时就生成一个dump文件,在服务使用一段时间后再生成一个dump文件,两个文件进行对比排除内存漏洞问题。

经过排查发现漏洞问题如下所示:



 


 发现问题出现在了JceSecurity的verificationResults 属性上,在verificationResults 属性中存在了过多的BouncyCastleProvider,随着应用的使用正在不断的增多,未被GC回收。

对javax.crypto.JceSecurity进行反编译查看代码发现verificationResults 是static 类属性,GC不会自动对其永久代进行回收。

对项目代码进行排查,发现项目中使用代码BouncyCastleProvider使用代码如下所示:

Cipher ci = Cipher.getInstance("RSA", new BouncyCastleProvider());

 发现是坑张的代码,在服务每次使用的时候都会重新创建一个BouncyCastleProvider用来进行初始化密钥的工具类。

public static final Cipher getInstance(String paramString, Provider paramProvider)
    throws NoSuchAlgorithmException, NoSuchPaddingException{
    if (paramProvider == null) {
      throw new IllegalArgumentException("Missing provider");
    }
    Object localObject1 = null;
    List localList = getTransforms(paramString);
    int i = 0;
    String str = null;
    for (Iterator localIterator = localList.iterator(); localIterator.hasNext(); ) {
      Transform localTransform = (Transform)localIterator.next();
      Provider.Service localService = paramProvider.getService("Cipher", localTransform.transform);
      if (localService == null)
        continue;
      Object localObject2;
      Object localObject3;
      if (i == 0){
        localObject2 = JceSecurity.getVerificationResult(paramProvider);
        if (localObject2 != null) {
          localObject3 = "JCE cannot authenticate the provider " + paramProvider.getName();

          throw new SecurityException((String)localObject3, (Throwable)localObject2);
        }
        i = 1;
      }
      if (localTransform.supportsMode(localService) == 0) {
        continue;
      }
      if (localTransform.supportsPadding(localService) == 0) {
        str = localTransform.pad;
      }
      try{
        localObject2 = (CipherSpi)localService.newInstance(null);
        localTransform.setModePadding((CipherSpi)localObject2);
        localObject3 = new Cipher((CipherSpi)localObject2, paramString);
        ((Cipher)localObject3).provider = localService.getProvider();
        ((Cipher)localObject3).initCryptoPermission();
        return localObject3;
      } catch (Exception localException) {
        localObject1 = localException;
      }
    }
    if (localObject1 instanceof NoSuchPaddingException) {
      throw ((NoSuchPaddingException)localObject1);
    }
    if (str != null) {
      throw new NoSuchPaddingException("Padding not supported: " + str);
    }
    throw new NoSuchAlgorithmException("No such algorithm: " + paramString, localObject1);
  }

 

可查看BouncyCastleProvider代码发现此类进行过特殊处理,每次new出的实例hashCode是相同的。又对JceSecurity.getVerificationResult方法代码进行了分析,代码如下所示:

static synchronized Exception getVerificationResult(Provider paramProvider){
    Object localObject1 = verificationResults.get(paramProvider);
    if (localObject1 == PROVIDER_VERIFIED)
      return null;
    if (localObject1 != null) {
      return (Exception)localObject1;
    }
    if (verifyingProviders.get(paramProvider) != null)
    {
      return new NoSuchProviderException("Recursion during verification");
    }Exception localException2;
    try {
      verifyingProviders.put(paramProvider, Boolean.FALSE);
      URL localURL = getCodeBase(paramProvider.getClass());
      verifyProviderJar(localURL);

      verificationResults.put(paramProvider, PROVIDER_VERIFIED);
      localException2 = null;

      return localException2;
    }
    catch (Exception localException1){
      verificationResults.put(paramProvider, localException1);
      localException2 = localException1;
      return localException2; } finally { verifyingProviders.remove(paramProvider); }
  }

 

 

查找到这里发现自己越来越矛盾,每次new出来的BouncyCastleProvider具有相同的hashCode,放在verificationResults 属性Map中怎么会越来越多,后一个应当会将前一个覆盖才对,怎么会导致内存溢出。

 

最终实在无头绪请教同事,发现一个verificationResults属性定义的居然是IdentityHashMap,此Map在存储类的时候并不是使用类的equals方法来判断是否Key已经存在,而是使用==来判断是否Key已经存在的。换句话说就是当两个对象不==那此Map就会将两个对象都存进去。

 

找到这里问题的解决方案就已经非常明了了,只要给BouncyCastleProvider定义成单例就可以了。

 

 

  • 大小: 110.3 KB
分享到:
评论
2 楼 ly_ltw 2017-01-22  
15018710182 写道
别用构造函数方法,换成
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

就OK了



但是如果我一个项目确实是有几千个不同的Provider呢?这个verifyingProviders定义成static是为了提升效率,可是不断的加在内存里不回收,这也不是个办法啊,难道sun他们就没有测出这个问题吗?
1 楼 15018710182 2017-01-18  
别用构造函数方法,换成
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

就OK了

相关推荐

Global site tag (gtag.js) - Google Analytics