实验的示例代码附在最后(jvisualvm , Jconsole- jdk8)
内存泄露通常表现为,使用的内存不断增长。
如果看到的类实例数,非java包类排在前10,就可能会有问题。结合jdk工具对长时间运行的j应用程序监控得到的数字,进行分析。
实验vm 参数:
-Xms3072M -Xmx3072M -Xmn512M -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=50 -XX:+PrintGCDetails -Xloggc:F:/mysql-5.7.20-winx64/gc.log
直观的看图:
1.不正常的图:
总堆内存趋势
CMS Old Gen 区域占用内存一直缓慢升高
PreparedStatement这个类的实例很多,所以,需要在程序中查找产生这个类例的代码,看有没有问题。
2.正常的图:
实验的关键代码是这段:(preStmt不关闭)
finally{ //这段代码是区分内存正常与否的一段,去掉,内存就会缓慢增长,直至OOM
try {
preStmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
另外:
去掉这个代码:
sb=null;//去掉内存占用情况会是锯齿状
System.gc();//去掉内存占用情况会是锯齿状
内存占用情况会是锯齿状
不调用GC代码:
sb=null;
//System.gc();//去掉内存占用情况会是锯齿状
调用GC代码:
sb=null;
System.gc();//去掉内存占用情况会是锯齿状
内存多少合适?
参数说明
-XX:SurvivorRatio: eden/单个survivor的比值, -XX:SurvivorRatio =1,eden大小和单个Survivor一样;-XX:SurvivorRatio =2,eden大小是单个Survivor的两倍大;-XX:SurvivorRatio =3,eden大小是单个Survivor的三倍大
-XX:CMSInitiatingOccupancyFraction=50:oldGen占用内存达到50%,cms起来干活,早点回做垃圾回收的动作。
理论上,对象的内存,在eden和survivor区域被回收是最好的情况,survivor的大小理论上应该和eden回收后活下来的对象差不多大,oldGen的大小应该差不多就是多次survivor,仍然活下来的对象差不多大。
根据上面的情况:eden区域的对象,大致在60M内,survivor应该也差不多在60以下,而oldGen里的对象基本稳定在15M左右。
所以这个程序应该要分配的内存,大致为:60(eden)+60*2(两个survivor)+15=200M左右内存。
jvm启动参数设置如下:
-Xms256M -Xmx256M -Xmn200M -XX:SurvivorRatio=2 -XX:MaxTenuringThreshold=3 -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=50 -XX:+PrintGCDetails -Xloggc:F:/mysql-5.7.20-winx64/gc.log
再看内存:
实际eden区在20M以内,oldGen在12M,而survivor基本没有使用。
再调参数:
20(eden)+1*2(两个survivor,不经过survivor)+15=40M左右内存。
-Xms40M -Xmx40M -Xmn25M -XX:SurvivorRatio=10 -XX:MaxTenuringThreshold=3 -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=50 -XX:+PrintGCDetails -Xloggc:F:/mysql-5.7.20-winx64/gc.log
看内存:
效果基本符合预期,oldGen占用比存比有点高,看着有点悬,可以适当加大一倍。
//实验示例代码(读取一个25G的数据文件,放到mysql数据库)
package com.ape.util.file;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Demo {
public static final String url = "jdbc:mysql://127.0.0.1/atii";
public static final String name = "com.mysql.jdbc.Driver";
public static final String user = "atii";
public static final String password = "123456";
public static Connection conn = null;
public static PreparedStatement preStmt = null;
static {
try {
if (conn == null) {
Class.forName(name);// 指定连接类型
conn = DriverManager.getConnection(url, user, password);// 获取连接
}
} catch (Exception e) {
e.printStackTrace();
}
}
static String sql = null;
static ResultSet ret = null;
static int lineNum = 0;
static int batchCount = 0;
public static void main(String[] args) {
// queryCount();
// deleteSql();
// querySql();//100791444
insertFromFile(10000000, 100);
}
public static void querySql() {
sql = "select * from t_at_ticketorder limit 0,10";// SQL语句
try {
preStmt = conn.prepareStatement(sql);// 准备执行语句
ret = preStmt.executeQuery(sql);// 执行语句,得到结果集
while (ret.next()) {
String uid = ret.getString(1);
String ufname = ret.getString(2);
String ulname = ret.getString(3);
String udate = ret.getString(4);
System.out.println(uid + "\t" + ufname + "\t" + ulname + "\t" + udate);
} // 显示数据
ret.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void deleteSql() {
sql = "TRUNCATE TABLE t_at_ticketorder";// SQL语句 TRUNCATE TABLE 表名
try {
preStmt = conn.prepareStatement(sql);// 准备执行语句
preStmt.executeUpdate(sql);// 执行语句,得到结果集
// db1.close();// 关闭连接
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void insertSql(String sql) {
System.out.println(batchCount++ +" :"+ sql.length());
if(batchCount%10==0){
try {
Thread.currentThread().sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
long starttime = System.currentTimeMillis();
try {
preStmt = conn.prepareStatement(sql);// 准备执行语句
preStmt.executeUpdate(sql);// 执行语句,得到结果集
// db1.close();// 关闭连接
} catch (SQLException e) {
System.out.println(sql);
System.out.println(lineNum);
writeToFile(sql);
e.printStackTrace();
}finally{ //这段代码是区分内存正常与否的一段,去掉,内存就会缓慢增长,直至OOM
try {
preStmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
long cost = (System.currentTimeMillis() - starttime) / 1000;
System.out.println("cost:" + cost);
try {
Thread.currentThread().sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void insertFromFile(int countNum, int perBatchNum) {
String head = "insert into t_at_ticketorder (ID, ORDERCD, MEMBERCD, STATUS, OPERATERID, CREATETIME, TASKID, ORDERSOURCE, CREATOR, CUSTREQUEST, FOREREMARK, MIDAUDIT, CANTREASON, CANTDESC, ATTACHORDERID, URGENCE, ISSUEDAY, ORDERTYPE, AUDITOR, AUDITTIME, INVOICEFLAG, PRINTINSFLAG, URGEMESSAGE, OPERATETIME, CONFIRMSTATUS, TKTLTIME, MEMBERNAME, MEMBERTYPE, HANDFLAG, OPENFLAG, BUYFLAG, LOGID, LOGTIME, DELIVERYINFOREMARK, VIPLEVEL, XO, DAXIA, SENDFAXTIME, HANDLECOUNT, ERRORFLAG, CLIENTCD, CLIENTORDERCD, AGENTID, HTORDERCD, MEMBERALIASID, MEMBERALIASNAME, TOMIDDLETIME, SCORE, INFANTREQUEST, SALESORDERID, FAILURECODE, RETURNORDERCD, TRAVELTYPE, UPGRADECLASS, NEWUPORDERCD, PSGTELFLAG, PRICEVALIDTIME, PRICEINVALIDTIME, RESERVEFLAG, UNITYORDERCD, UNITYITEMCD, REQUEST_PAYTIME, BOOKINGCLOSED, AIRLINEORDERCD, MBRSHIPCATEGORYCD, ISVALID, IP, AUTOCANCELFLAG, UK, SK, VERSION, AGENTORDERCD, MGUID, MGSID, CANCELTYPE, CANCELREASON) values \n";
StringBuffer sb = new StringBuffer(70605).append(head);
int count = 0;
try {
// read file content from file
FileReader reader = new FileReader("D:/task/2017/db_bak/t_at_ticketorder.sql");
BufferedReader br = new BufferedReader(reader);
String str = null;
while ((str = br.readLine()) != null) {
lineNum++;
// if (lineNum > 15882) {
// // System.out.println(str);
// } else {
// // System.out.println(str);
// }
// str = new String(str.getBytes(), "utf8");
if (str.indexOf("insert into") > -1) {
count++;
continue;
}
if (count == 1 || count % perBatchNum == 0) {
if (count > countNum) {
break;
}
sb.append(str.replace("values", "").replace("');", "'),").replaceAll("to_date", "str_to_date")
.replaceAll("dd-mm-yyyy hh24:mi:ss", "%d-%m-%Y %H:%i:%s")
.replaceAll("dd-mm-yyyy", "%d-%m-%Y").replaceAll("\\?", "").replaceAll("\\\\", "") + "\n");
if (count % perBatchNum == 0 && str.indexOf("');") > -1) {
insertSql(sb.substring(0, sb.length() - 2));
sb=null;//去掉内存占用情况会是锯齿状
System.gc();//去掉内存占用情况会是锯齿状
sb = new StringBuffer(70605).append(head);
}
str=null;//去掉内存占用情况会是锯齿状
continue;
}
sb.append(str.replace("values", "").replace("');", "'),").replaceAll("to_date", "str_to_date")
.replaceAll("dd-mm-yyyy hh24:mi:ss", "%d-%m-%Y %H:%i:%s").replaceAll("dd-mm-yyyy", "%d-%m-%Y")
.replaceAll("\\?", "").replaceAll("\\\\", "") + "\n");
str=null;//去掉内存占用情况会是锯齿状
}
br.close();
reader.close();
} catch (Exception e) {
System.out.println(count);
e.printStackTrace();
}
// return sb.substring(0, sb.length()-2);
}
public static void writeToFile(String sb) {
try {
// write string to file
FileWriter writer = new FileWriter("E:/mango/t_at_ticketorder/test2.txt");
BufferedWriter bw = new BufferedWriter(writer);
bw.write(sb);
bw.close();
writer.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void queryCount() {
sql = "select count(*) from t_at_ticketorder";// SQL语句
try {
preStmt = conn.prepareStatement(sql);// 准备执行语句
ret = preStmt.executeQuery(sql);// 执行语句,得到结果集
while (ret.next()) {
String uid = ret.getString(1);
System.out.println(uid);
} // 显示数据
ret.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}