频繁createBitmap导致的OOM问题记录

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了频繁createBitmap导致的OOM问题记录相关的知识,希望对你有一定的参考价值。

参考技术A

最近在做一个相机有关的应用,相机输出的一般来说都是需要旋转才能变为我们看来正向的图像的,这是因为相机传感器的坐标体系和屏幕不一致造成的,具体的不展开说了。但是图像输出导致的一系列问题值得记录一番。

首先是图像预览输出很慢很卡,这时候就要注意一下, ImageReader 的输出格式不要使用 ImageFormat.JPEG ,可以使用 ImageFormat.YUV_420_888 ,我猜测原因是 JPEG 格式输出的话,相机还得为我们做一次转码,在低性能手机上很容易导致卡顿,如果改了格式还卡的话,考虑一下降低图像输出的分辨率吧。至于 YUV 转 Bitmap 的话,可以网上找找 C 的库,性能比直接输出 JPEG 转 Bitmap 更高。

接下来就是图像的方向和屏幕不一致了,网上搜索旋转 Bitmap ,基本上都是说使用

来创建一个新的 Bitmap ,然后将原 Bitmap 释放掉,这时候坑就来了,在平时这样使用是没事的,但是在相机预览输出的时候就有问题了,因为相机预览输出速度很快,这种情况下 Bitmap 是来不及释放掉的,会慢慢的内存泄漏,直到最后出现内存溢出。而且 Bitmap 的创建时非常消耗内存的,这样频繁创建 Bitmap ,内存的消耗是飞速的,这时候我们可以选用另外一种方式,网上很少有人提到的方法来旋转 Bitmap ,这个方法就是使用 Canvas 。

我们知道, Canvas 创建的时候可以传入 Bitmap ,这时候 Canvas 上的所有绘制都会直接绘制在 Bitmap 上,而且 Canvas 也有 drawBitmap 方法,可以用其他 Bitmap 做画笔,更关键的是, drawBitmap 的重载方法里有一个是可以传入 Matrix 的,这时候解决方法就出来了,我们可以创建一个全局复用的 Bitmap ,注意大小使用旋转后的大小,然后使用它来创建 Canvas ,之后所有 bitmap 旋转都不用再生成新的 bitmap ,只要使用 canvas.drawBitmap 在全局 Bitmap 上画就行。不过事情还没完。

通过 Bitmap 创建的 Canvas 的画布大小就是 Bitmap 的大小,我们旋转 bitmap 的时候要注意, Matrix 旋转时画笔 Bitmap 的位置是会发生变化的,最后画出来能输出到 Bitmap 上的可能只有小部分,甚至完全没有画在 Bitmap 上。这时候我们就需要在 Matrix 里面添加位移操作,来保持画笔输出位置和画布一致了,具体的位移距离就得自己算了,因为线代我已经忘干净了,所以我的做法是,拿两张纸,自己旋转完看位置......

到此,相机图像输出导致的所有问题都解决了,写出来就这么几步,但是摸索的时候也卡了我老半天,因为网上没有搜到相关的问题记录,所以记录下来以免忘记。

——在 YUV 转 Bitmap 可以使用 libyuv 库,同时旋转操作也可以通过该库完成,如果可以,最好不要使用Java做旋转

记录一将内存中大量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 下载

以上是关于频繁createBitmap导致的OOM问题记录的主要内容,如果未能解决你的问题,请参考以下文章

Android开发技术周报183学习记录

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

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

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

FlinkFlink 写入 Clickhouse 大对象直接进入老年代 导致OOM

龙叔运维问题排查记录异常数据导致weblogic线程stuck/OOM