偷懒的垃圾回收

最近应需求写了一个oracle到mysql的数据导入工具,本来想随便写写,直接把数据一次读到内存然后再写。后来发现性能略差,就试图做点性能优化,使用IDataReader,一边读一边写,然而mysql写速度根本跟不上oracle读速度,而且测试机只有双核4线程,开4个写线程CPU75%,6个写线程就95%了。

不过没有用到sql优化,mysql插入优化主要是拼接sql,比如说在values后面拼1000个()。oracle就比较方便,参数绑定可以支持数组,这样就可以批量执行。类似于这样。

public int OracleExecuteBatch(string sql, List<DbType> types, List<List<object>> paramList)
{
	if (string.IsNullOrWhiteSpace(sql))
	{
		return -1;
	}
	string commandText = "";
	int num = 0;
	if (!this._ParseSQL(sql, ref commandText, ref num))
	{
		return -1;
	}
	OracleCommand oracleCommand = new OracleCommand();
	oracleCommand.Connection = (OracleConnection)this.mCon;
	oracleCommand.CommandText = commandText;
	for (int i = 0; i < types.Count; i++)
	{
		DbParameter dbParameter = oracleCommand.CreateParameter();
		dbParameter.DbType = types[i];
		dbParameter.Value = paramList[i].ToArray();
		dbParameter.ParameterName = this.mParamNamePre + i;
		oracleCommand.Parameters.Add(dbParameter);
	}
	int count = paramList[0].Count;
	oracleCommand.ArrayBindCount = count;
	return oracleCommand.ExecuteNonQuery();
}

当然我想解决的问题不是这个问题,我的工具在数据同步完成后内存没有下降,这让我怀疑我是不是哪里把内存写漏?拿VS远程调试把进程停下来也没发现有几个线程,于是只能祭出Windbg。

前文参考文档,先载入dump文件(其实可以直接调试进程),然后输入 !DumpHeap -stat 分析托管对象。过了10几分钟后才得到结果,原来里面还有几十亿个临时对象没有释放。ProcessExplorer 显示二代堆占用了大部分内存。

然后点击第一列 windbg 会自动输入 !DumpHeap /d -mt 000007fe8a61e3a0 这样就会打印这个类型所有对象,于是就打印了一个多小时(

点击对象前地址会自动输入 !DumpObj /d 00000003c359f750,这就打印了对象的所有信息

用 !gcroot 检查这个对象的引用情况,发现没有任何引用(

然后拿 vs 远程调试暂停下进程,输入 System.GC.Collect() 内存瞬间释放(

结论:垃圾回收好尼玛偷懒,线程长时间运行时,对象会移动到二代堆,然而等到线程退出时虚拟机也不好好回收一下,毕竟系统内存还有富余,最后只能定时主动调用 System.GC.Collect() 以达到回收内存的目的。

 

参考:
WinDBG找出你内存溢出的地方

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注