JVM学习笔记:[url]http://blog.csdn.net/cutesource/article/details/5904501[/url]
堆内存设置原理:[url]http://blog.csdn.net/sivyer123/article/details/17139443/[/url]
JVM 之 GC日志分析 :[url]http://blog.csdn.net/lan861698789/article/details/51985188[/url]
JVM client模式和Server模式的区别:[url]http://developer.51cto.com/art/201009/228035.htm[/url]
垃圾收集器:[url]http://yueyemaitian.iteye.com/blog/1185301[/url]
VisualVM分析器:[url]http://www.cnblogs.com/linghu-java/p/5689227.html[/url]
MemoryAnalyzer使用:[url]http://wensong.iteye.com/blog/1986449[/url]
MAT中Retained和Shallow区别:[url]http://bjyzxxds.iteye.com/blog/1532937[/url]
应用场景:先需要将Mdb中的表中数据,全量迁移到Sdb中,一种方法用工具Kettle,第二种编写脚本,第三种程序迁移,第一与第二种迁移对大表没有问题,第三种,由于百万级数据表,全量从Mdb取出,再全量插入的Sdb,对内存的要求较高,比较有效的方法是分页查询,再插入;今天我们不讲的分页查询插入,来看全量从Mdb取出,再分批量插入的Sdb,关于分页分批插入(为应对内存问题,可以将查询结果存到文件,再从文件中读取插入),后续再讲。
主要代码:
int counts =0;//记录数
//Data d = null;
while (rs.next()) {
Data d = new Data();
d = getData(rs);
insertList.add(d);
pd = null;//待gc回收
if(counts%5000==0){
batchSave(insert, insertList);
insertList.clear();
log.info("============RecordsSave:"+counts);
}
if(rs.isLast()){
batchSave(insert, insertList);
insertList.clear();
}
log.info("============Records:"+counts);
counts++;
}
下面的测试表记录数为28万
Tomcat:JavaOPT 默认
-client
-Xmx256M
JVM虚拟机默认启动时,client模式,垃圾模式为Serial New + Serial Old
当counts%10000==0,批量为10000,抛出OOM:java heap space
当Data d = new Data();放在循环外定义:Data d = null;抛出OOM:java heap space
当批量为5000时:
JConsole 堆信息:
2016-09-29 16:40:42
已使用: 107,876 Kb
分配: 253,440 Kb
最大值: 253,440 Kb
GC 时间:
Copy(326 项收集)所用的时间为 2.518 秒
MarkSweepCompact(171 项收集)所用的时间为 1 minute
所用时间s:220.66
VisualVM:
[img]http://dl2.iteye.com/upload/attachment/0120/3409/a119f4f0-c62b-3f04-af66-465eda9f3292.png[/img]
Viusal GC:
[img]http://dl2.iteye.com/upload/attachment/0120/3411/b9f3c452-a9d9-3f3c-9d96-cc40b39d63c9.png[/img]
去除log.info("============Records:"+counts);
JConsole 堆信息:
时间: 2016-09-29 16:25:44
已使用: 244,748 Kb
分配: 253,440 Kb
最大值: 253,440 Kb
GC 时间:
Copy(332 项收集)所用的时间为 2.456 秒
MarkSweepCompact(114 项收集)所用的时间为 39.902 秒
所用时间s:163.09
这说明,不要打,不必要的log
VisualVM:
[img]http://dl2.iteye.com/upload/attachment/0120/3413/2667c4b2-68ad-3b58-b988-ddc6cf74e925.png[/img]
Viusal GC:
[img]http://dl2.iteye.com/upload/attachment/0120/3415/4dd11eba-7a59-3f69-8e81-6351148c817a.png[/img]
当批量为2000时:
JConsole 堆信息:
2000
时间: 2016-09-29 16:50:28
已使用: 252,652 Kb
分配: 253,440 Kb
最大值: 253,440 Kb
GC 时间:
Copy(321 项收集)所用的时间为 2.369 秒
MarkSweepCompact(106 项收集)所用的时间为 36.438 秒
所用时间s:145.524
VisualVM:
[img]http://dl2.iteye.com/upload/attachment/0120/3417/4a0516d3-13a7-3da3-8efc-0fb0afb0dbba.png[/img]
Viusal GC:
[img]http://dl2.iteye.com/upload/attachment/0120/3419/adef8854-e906-3c92-abe6-efd5aaf03c96.png[/img]
对比5000可以看出,堆内存的趋势图,更平缓,Copy及MarkSweepCompact收集次数减少,故所用时间降低。
当批量为1000时:
JConsole 堆信息:
时间: 2016-09-29 17:03:10
已使用: 249,215 Kb
分配: 253,440 Kb
最大值:253,440 Kb
GC 时间:
Copy(330 项收集)所用的时间为 2.299 秒
MarkSweepCompact(100 项收集)所用的时间为 34.530 秒
所用时间s:159.459
从所用时间和Copy及来看,比2000批次还有,MarkSweepCompact收集次数减少了6次
从5000,2000,1000来看说明,批次处理量对处理的速度有影响,应选择合适的批次数量。
下面调整一下
JavaOPT
-server
-XX:+PrintGCDetails
-Xloggc:E:\gc.log
Server模式下,垃圾回收器默认为PS Scavenge+ PS-Old
我的机器内存是8G,默认Server模式下,Xmx1024M ,同样的处理逻辑:
当批量为2000时:
JConsole 堆信息:
时间: 2016-09-29 17:12:41
已使用: 337,969 Kb
分配: 574,784 Kb
最大值: 932,096 Kb
GC 时间:
PS MarkSweep(6 项收集)所用的时间为 1.475 秒
PS Scavenge(74 项收集)所用的时间为 1.090 秒
全量更新所用时间s:114.25
当批量为5000时:
时间: 2016-09-29 17:19:26
已使用: 365,969 Kb
分配: 547,968 Kb
最大值: 932,096 Kb
GC 时间:
PS MarkSweep(6 项收集)所用的时间为 1.400 秒
PS Scavenge(70 项收集)所用的时间为 1.212 秒
所用时间s:107.12
从上可以看出新生代的收集次数减少,所用时间减少,这说明当堆内存增大时,可以相应的调整批量处理数,以提高处理效率。
我们来看看在-server,模式下能处理的最大记录数:
java OPTS:
-server
-Xms512M
-Xmx1024M
-XX:NewSize=384M
-XX:ParallelGCThreads=4
-XX:+PrintGCDetails
-Xloggc:E:\gc.log
时间: 2016-09-29 18:16:19
已使用: 1,004,657 Kb
分配: 1,028,288 Kb
最大值: 1,028,288 Kb
GC 时间:
PS MarkSweep(424 项收集)所用的时间为 10 minutes
PS Scavenge(121 项收集)所用的时间为 3.631 秒
在上述配置下,能够处理的最大记录数为1040000
总结:
从上的分析来看,批量处理的数量,应该根据堆内存的变化而调整,已达到立项的处理效率;
在1G的堆内存,处理百万级以下的数据量,还是可以滴。
分页分批次处理:
java opts:
-server
-Xms1024M
-Xmx1536M
-XX:NewSize=512M
-XX:ParallelGCThreads=4
主要逻辑:
sql = "SELECT COUNT(*) FROM " + test;
ps = con.prepareStatement(sql);
countResultSet = ps.executeQuery();
int sums =0;
while(countResultSet.next()){
sums =countResultSet.getInt(1);
}
int batches = 0;
if( sums > 100000){
if(sums % 100000 ==0){
batches = sums/100000;
}
else{
batches = sums/100000 + 1;
}
}
int counts =0;//记录数
for(int i =1;i<=batches;i++){
logger.info("==============第"+i+"页start==========");
counts+=InsertRecordByPages((i-1)*100000+1,(i)*100000);
logger.info("==============已更新记录数:"+counts);
logger.info("==============第"+i+"页end==========");
}
在InsertRecordByPages函数中,不用的局部变量要null化
结果:可以处理140万的数据
==============第15页start==========
2016-09-30 16:16:10 -885050 ==============已更新记录数:1409864
2016-09-30 16:16:10 -885050 ==============第15页end==========
再大,只能用工具迁移了。