欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

内存泄漏问题总结

程序员文章站 2022-03-24 09:15:49
...
  1. 同事有一个带有少量业务逻辑然后更新数据库某字段的需求(大约900万数据)
  2. 执行了几千条后,发现日志停住了。
  3. 使用jvisualvm查看GC,发现Old区和Eden区都已经满了
  4. 第一反应是可能存在内存泄漏,但是看到系统初始化参数里面最大堆内存大小只有512M,就觉得调大堆内存就应该可以了。
  5. 所以把堆内存大小调整为4G,Eden区2G,重启,正常运行了
  6. 第二天早上看日志,发现又停了,日志报内存溢出。查看GC,Old和Eden又满了
  7. 一定是有内存泄漏
  8. show me the code:
public class UpdateUtil {

    private static final Logger logger = LoggerFactory.getLogger(UpdateUtil.class);

    static String sql = null;
    static Connection con = DBHelper.getDBHelper();
    static ResultSet ret = null;
    static PreparedStatement pst = null;

    public static void updateHyId() {
        try {
            for (int i = 4000000; i < 10000000; i = i + 1000) {
                sql = "select id,company_name,scope from t_info_recruit_gongshang_2015pre limit " + i + ",1000" ;// SQL语句
                pst = con.prepareStatement(sql);// 准备执行语句
                ret = pst.executeQuery();// 执行语句,得到结果集
                while (ret.next()) {
                    String id = ret.getString(1);
                    String companyName = ret.getString(2);
                    String scope = ret.getString(3);
                    logger.info("获取数据[id:" + id + ",companyName:" + companyName + ",scope:" + scope+"]");
                    Long hyid = HuangyeUtil.getHuangyeCateId(companyName, scope);
                    if (null != hyid) {
                        sql = "update t_info_recruit_gongshang_2015pre set 58_cate2_id_hy=" + hyid + " where id=" + id;
                        pst = con.prepareStatement(sql);
                        int count = pst.executeUpdate();
                        if (count > 0) {
                            logger.info("更新数据[id=" + id + ",hyid=" + hyid+"]");
                        }
                    }
                }

            }
            // 显示数据
            ret.close();
            con.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

public class DBHelper {
    public static final String url = "jdbc:mysql://pro-zpdm.db.58dns.org:3784/dm_busi";
    public static final String name = "com.mysql.jdbc.Driver";
    public static final String user = "busi_miswr";
    public static final String password = "2ee3126b1669dd58";

    public static Connection conn = null;

    public static Connection getDBHelper() {
        try {
            Class.forName(name);// 指定连接类型
            conn = DriverManager.getConnection(url, user, password);// 获取连接
        } catch (Exception e) {
            e.printStackTrace();
        }
        return conn;
    }

}
  1. 显然,上面的代码有两个问题:1)在循环里面创建了多个preparedStatement和resultSet,但是关闭操作却在循环外 2)preparedStatement和resultSet的引用是一个类静态变量,生命周期与系统生命周期一致,那样的话,循环里面生成的对象指向的引用一直存在,这些对象就不能被GC。
  2. 使用jmap生成堆栈信息,使用jhat分析:
    内存泄漏问题总结
  3. 印证了上面的分析,所以应该把pst和ret改为循环内的局部变量。
  4. 改为循环内的局部变量,再测试,好像还是不对,这PreparedStatement和ResultSet的对象还是很多,很大,使用jmap生成堆栈数据的时候,执行了2次full gc,但是Old区已使用内存没见减小。
  5. 那每次用完的PreparedStatement和ResultSet都设置为null呢?测试,还是不对
  6. 是不是connnection不关闭,引用这个connection的PreparedStatement和ResultSet就不会被回收呢?把数据量调低为50万,运行,通过jmap观察到了这两个对象已经涨得很多了,full gc并没有回收他们;但是当这50万运行完,再执行jmap的时候,发现Old区一下子降下来了,PreparedStatement和ResultSet也被立即回收了。
  7. 由于PreparedStatement存在connnection的引用,connnection不能被回收,PreparedStatement就也不能被回收。
相关标签: 内存溢出