0x00 问题初现
公司一个C/S产品,使用的JavaFX进行开发,用户面板展示了一个罐的动态效果图,类似这种的:
高度波浪是动态的效果图,前端小哥整的GIF。
因为需要使用某一款只有32位驱动的读卡器所以jre也就选择使用32位的了,开发测试都正常进行中,有一天 测试同事说 他新加的罐不显示了,最多只能显示XX个,我心想不应该啊,不记得哪里设置了对显示数目的设置,赶紧去巴拉巴拉代码,也没发现限制显示的逻辑,在我机器上启动后,同样数量的罐也能正常显示。疑惑中猛然想起我机器上暂时不用读卡器所以切换到64位的jdk了,于是更改为32位后本地复现下这问题,果然报错了:
堆溢出了!!!
0X01 定位问题
堆栈异常信息提示是在GIFImageLoader2中加载gif时出现的异常,这时候基本有些感觉了,应该是堆分配不了更多的空间导致的溢出。可是我的gif图只有1M左右大小怎么会才显示几个罐就直接用完空间了呢?好吧,继续查!
上jvisualvm 为了方便演示,在vm启动参数中加上 -Xmx200m
在加载gif动图前一直是30M左右,开始加载后,马上开始飙升,直接干到了上限处,然后就报了OOM异常。
问题很明显了,就是本来一个1M左右的gif加载渲染时却占用了将近50M的内存!32位jre默认最大的heapsize 是 700M左右,64位的是4G左右;现在问题的关键就是搞明白为什么 加载gif时内存用了50倍!
0x02 继续巴拉代码
一个gif图片只有不到1M的大小,既然是翻了50倍说明应该是有 多次加载或者循环加载的方法,根据异常信息快速定位过去:
这里只有在image==null的时候才会break出去,再往下:
看waitForImageFrame的方法:
到了关键的地方了,显示每次遇到0x2c就会返回加载,只有遇到0x3B时才会返回-1 结束图片的加载,那么这俩是什么呢?
百度走起~~~
通过查看相关资料真相了,gif图片可以理解成是由多个图片根据某些压缩算法(比如LZW算法)合成的,而JavaFX在加载gif图时,是通过0X2C来一幅一幅的加载到ImageFrame ,遇到0x3B结束加载,然后才渲染的,可以理解成将图片解压缩了,然后一幅一幅的图片 通过Animation加载的,这就导致了本来是压缩的gif实际渲染时占用内存翻了很多倍。
0x03 解决办法
目前想到的最快的解决办法就是 减少gif中图片的数量,也就是减少 0x2c,尽快解决的话,直接ps打开:
这里也验证了上面渲染解压缩了的结论,可以通过删除中间一些变化小的桢图,加快动画速度的方式,对图片进行压缩。
再次验证,图片也都可以加载出来了,并且内存占用也大大的降低了。
0x04 扩展想法
这里JavaFX加载gif的方式明显是不合理的,当然因为现在也基本没多少人使用这个框架了,导致很少维护了。每次加载个压缩后不到1M 渲染却需要 count(0x2c) 数量的内存,这大大的浪费了空间。
接下来尝试下 使用不同的渲染方式,实现空间利用率的提升。
未完待续…
【参考文章】https://blog.csdn.net/linghu_java/article/details/14002413