java程序运行时,内存溢出了,会报错误:java.lang.OutOfMemoryError: Java heap space。
一些操作会持续消耗大量内存,通过设置JVM的内存大小不能从根本上解决内存溢出的问题。下面说两个我遇到的内存溢出问题和解决办法。
1. 集合对象元素过多
- 背景:
两个线程,线程A向一个队列里加入元素,线程B从同一个队列取出元素,写入的速度比取出的快,一段时间后内存溢出。 - 问题定位:
这种情况定位问题比较麻烦,因为内存不是一次性爆的,而是慢慢加到爆的。控制台打印出来的出错那一行可能只是赋值了一个对象, 而真正的错误原因是因为队列太大。所以要定位这类问题,需要特别关注一下集合对象放入和取出元素的操作。 - 解决思路:
控制集合大小不靠谱,既然是内存的问题,就要以内存作为控制条件。在每次向集合中插入新元素之前,判断集合是否超过某个阈值(如500M),如果超过,则让插入的线程休眠,等待另一线程取出集合中的元素,至集合大小低于某一阈值(如350M)后再继续插入。 - 具体方案:
主要解决计算对象占用内存大小的问题。可以把对象转为字节数组,再得到这个数组的大小。可以用对象流,也可以用Jackson-mapper这样的东西。
2. 单个结果集对象过大
- 背景:
在对数据库的操作中,有时需要获取一个很大的结果集,保存到ResultSet对象中。这个结果集如果比jvm设定的内存大,就会造成内存溢出。 - 解决思路:
对于mysql,JDBC的URL这样写:
jdbc:mysql://localhost/:3306/test?useCursorFetch=true&
defaultFetchSize=500
主要是加上useCursorFetch和fetchsize和这两个参数。设置使用游标遍历数据和游标每次遍历的条数,这样就不会把结果集全部cache到内存里,就避免了内存溢出的问题。
这样设置过后,程序没有内存溢出的错误了,但是会导致另外一个问题:响应慢,一段时间才有数据返回。
这个问题可能的解释是,mysql要等游标遍历完成才开始返回数据。这样一来,传输数据的时候,迟迟看不到数据开始传,用户体验不好;另一方面,效率也较低,如果需要重启传输什么的,就更崩溃了。
对于这个问题,在java中设置statement,用流的方式接受数据就可以解决:
stmt = (com.mysql.jdbc.Statement) con.createStatement();
stmt.enableStreamingResults();
注意这是mysql才有的问题,enableStreamingResults也是mysql的JDBC才有的。
对于其他数据库,我就不懂了….Oracle的话,据说默认不会把结果集cache到内存,一般不会有内存溢出的问题。而且Oracle有快速返回机制,可以马上返回已经获取到的结果。DB2和SQLServer就是一点都不懂了….
放两篇参考的博客:
http://soft.chinabyte.com/database/258/12609258.shtml
http://blog.sina.com.cn/s/blog_670620330101n8dz.html