记录一将内存中大量JceSecurity实例导致OOM问题排查

Posted 赵侠客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记录一将内存中大量JceSecurity实例导致OOM问题排查相关的知识,希望对你有一定的参考价值。

问题现象

最近有一个同事负责的项目,运行一断时间就OOM了,让我帮忙排查一下是什么原因,首先我查看项目启动命令配置了dump文件,看到dump文件居然有3.4G,肯定是代码有问题导致内存没有回收。

-XX:+HeapDumpBeforeFullGC -XX:+HeapDumpAfterFullGC -XX:HeapDumpPath=\\dump

排查过程

选择Biggest Objects,看到javax.crypto.JceSecurity这个对象居然有1.9G

选择javax.crypto.JceSecurity 然后 选择Use Selected Objects,References 里选择Outgoing references

然后可以看到verificationResults 这个HasMap里居然有14349个BouncyCastleProvider对象,看来就是这个对象没有释放导致内存泄露了。
e
在项目里搜索了一下BouncyCastleProvider,这个类是在RSAUtill被实例化了,看来是在做RSA加解密时出现的问题


RSA解密代码如下,应该就是 Cipher.getInstance(“RSA”,new org.bouncycastle.jce.provider.BouncyCastleProvider()) 这行代码导致的

	public static byte[] decrypt(PrivateKey pk, byte[] raw) throws Exception    
	    try    
	    	//这里是RSA解密,每解密一次创建了一个对象
	        Cipher cipher = Cipher.getInstance("RSA",   
	                new org.bouncycastle.jce.provider.BouncyCastleProvider());   
	        cipher.init(cipher.DECRYPT_MODE, pk);   
	        int blockSize = cipher.getBlockSize();   
	        ByteArrayOutputStream bout = new ByteArrayOutputStream(64);   
	        int j = 0;
	        while (raw.length - j * blockSize > 0)    
	            bout.write(cipher.doFinal(raw, j * blockSize, blockSize));   
	            j++;   
	           
	        return bout.toByteArray();   
	     catch (Exception e)    
	        throw new Exception(e.getMessage());   
	       
	   

进入 Cipher.getInstance()方法中,关键有一行代码 Exception ve = JceSecurity.getVerificationResult(provider);

    public static final Cipher getInstance(String transformation,
                                           Provider provider)
            throws NoSuchAlgorithmException, NoSuchPaddingException
    
        if (provider == null) 
            throw new IllegalArgumentException("Missing provider");
        
        Exception failure = null;
        List<Transform> transforms = getTransforms(transformation);
        boolean providerChecked = false;
        String paddingError = null;
        for (Transform tr : transforms) 
            Service s = provider.getService("Cipher", tr.transform);
            if (s == null) 
                continue;
            
            if (providerChecked == false) 
            	//此处获取了provider
                Exception ve = JceSecurity.getVerificationResult(provider);
                if (ve != null) 
                    String msg = "JCE cannot authenticate the provider "
                        + provider.getName();
                    throw new SecurityException(msg, ve);
                
                providerChecked = true;
            
            if (tr.supportsMode(s) == S_NO) 
                continue;
            
            if (tr.supportsPadding(s) == S_NO) 
                paddingError = tr.pad;
                continue;
            
            try 
                CipherSpi spi = (CipherSpi)s.newInstance(null);
                tr.setModePadding(spi);
                Cipher cipher = new Cipher(spi, transformation);
                cipher.provider = s.getProvider();
                cipher.initCryptoPermission();
                return cipher;
             catch (Exception e) 
                failure = e;
            
        

        // throw NoSuchPaddingException if the problem is with padding
        if (failure instanceof NoSuchPaddingException) 
            throw (NoSuchPaddingException)failure;
        
        if (paddingError != null) 
            throw new NoSuchPaddingException
                ("Padding not supported: " + paddingError);
        
        throw new NoSuchAlgorithmException
                ("No such algorithm: " + transformation, failure);
    

在JceSecurity.getVerificationResult(provider) 找到了 verificationResults.put(p, PROVIDER_VERIFIED),每解密一次,就会像verificationResults对象中put一次provider对象,怪不得verificationResults 这个HasMap里居然有14349个BouncyCastleProvider对象。原来都找到了。

解决方法

将 Cipher 改写成单例就好了

    private static Cipher cipher;
    static 
        try 
            cipher = Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
         catch (Exception e) 
            e.printStackTrace();
        
    

提示

javax.crypto.Cipher 自带的JDK没有原码 ,经查阅资料发现,jdk加密相关的类javax.crypto.Cipher不提供源码文档,原因是受美国政府强加密算法出口管制限制而不提供源代码。美国政府对涉及到加密算法和强加密的软件技术产品进行出口管制和限制,基于这个法律的要求,JDK 不能提供有关强加密的源代码。不过可以从 https://jdk.java.net/java-se-ri/8-MR3 下载

以上是关于记录一将内存中大量JceSecurity实例导致OOM问题排查的主要内容,如果未能解决你的问题,请参考以下文章

记录一将内存中大量JceSecurity实例导致OOM问题排查

ObjectMapper SerializerCache 的单个实例中的对象过多导致内存泄漏

静态内部类解决内存泄漏

能否使用require('.json')的方式加载大量JSON文件?

JAVA I/O内存映射文件

使用一个策略之一将记录发送到消息队列