内存泄漏问题总结
程序员文章站
2022-03-24 09:15:49
...
- 同事有一个带有少量业务逻辑然后更新数据库某字段的需求(大约900万数据)
- 执行了几千条后,发现日志停住了。
- 使用jvisualvm查看GC,发现Old区和Eden区都已经满了
- 第一反应是可能存在内存泄漏,但是看到系统初始化参数里面最大堆内存大小只有512M,就觉得调大堆内存就应该可以了。
- 所以把堆内存大小调整为4G,Eden区2G,重启,正常运行了
- 第二天早上看日志,发现又停了,日志报内存溢出。查看GC,Old和Eden又满了
- 一定是有内存泄漏
- 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)在循环里面创建了多个preparedStatement和resultSet,但是关闭操作却在循环外 2)preparedStatement和resultSet的引用是一个类静态变量,生命周期与系统生命周期一致,那样的话,循环里面生成的对象指向的引用一直存在,这些对象就不能被GC。
- 使用jmap生成堆栈信息,使用jhat分析:
- 印证了上面的分析,所以应该把pst和ret改为循环内的局部变量。
- 改为循环内的局部变量,再测试,好像还是不对,这PreparedStatement和ResultSet的对象还是很多,很大,使用jmap生成堆栈数据的时候,执行了2次full gc,但是Old区已使用内存没见减小。
- 那每次用完的PreparedStatement和ResultSet都设置为null呢?测试,还是不对
- 是不是connnection不关闭,引用这个connection的PreparedStatement和ResultSet就不会被回收呢?把数据量调低为50万,运行,通过jmap观察到了这两个对象已经涨得很多了,full gc并没有回收他们;但是当这50万运行完,再执行jmap的时候,发现Old区一下子降下来了,PreparedStatement和ResultSet也被立即回收了。
- 由于PreparedStatement存在connnection的引用,connnection不能被回收,PreparedStatement就也不能被回收。
上一篇: ShellCode —— 入门
下一篇: Git命令之分支详解