前几天发现公司一个服务占用了大量的内存,有时候达到7-8G,服务直接就挂了。后面发现这个服务里一张7-8w数据的表用efcore查询,随着高并发的访问内存飙高,访问结束后一定量的内存没有得到释放,后面直接就飚到了3个G。后面我本地用控制台做了多线程的efcore的查询,发现线程结束后GC并没有回收完数据,有时候剩下还几个G的数据都在。有哪位大佬碰到过这个问题吗?
建议检查一下有没有将使用 DbContext 的类注册为 Singleton ?
没,直接用的using。
@Mrs_ZMx: 建议看一下 EF Core 的告警日志,可能是某些 LINQ 造成了加载全表数据在内存中查询
@dudu: 我控制台在本地循环用线程池开10个线程,每个线程来拖一张1W的表,内存直接飚到4G,最后有2G的内存一直没被GC,找了很久,找不到问题。
@dudu: 不开线程,一切正常。
@Mrs_ZMx: 建议最好能提供重现这个问题的实例代码
@dudu: static void Main(string[] args)
{
for (int i = 0; i < 400; i++)
{
var task = new Task(() => { var d = efoscoreContext.Project.ToList(); });
task.Start();
}
while (true)
{
Console.WriteLine("主线程的id:" + Thread.CurrentThread.ManagedThreadId + "--现在存在的线程数;" + Process.GetCurrentProcess().Threads.Count);
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}
@dudu: static void Main(string[] args)
{
for (int i = 0; i < 400; i++)
{
var task = new Task(() =>
{
using (efoscoreContext efoscoreContext = new efoscoreContext(new DbContextOptionsBuilder<efoscoreContext>().UseSqlServer("******;").Options))
{
var d = efoscoreContext.Project.ToList();
}
});
task.Start();
}
while (true)
{
Console.WriteLine("主线程的id:" + Thread.CurrentThread.ManagedThreadId + "--现在存在的线程数;" + Process.GetCurrentProcess().Threads.Count);
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}
@dudu: 测试代码就是这样,我拉project这张表,执行过程会GC,但是线程结束后,一部分内存回收不了
@Mrs_ZMx: 推荐阅读2篇博文:
@Mrs_ZMx: 建议试试
1)使用 WeakReference
WeakReference d = efoscoreContext.Project.ToList();
2)使用 GC.Collect()
GC.Collect();
while (true)
{
//..
}
@dudu: WeakReference好像不能使用在efoscoreContext.Project.ToList()上,GC.Collect是可以的,但这影响性能.我等下准备写篇博客,把详细的问题的过程陈述过来。现在我只有重启服务来解决问题了
@Mrs_ZMx: 等你的博文
@dudu: 简单写了一篇随笔
@dudu: https://www.cnblogs.com/tcdbk/p/12028106.html
@Mrs_ZMx: 下面的代码测试没这个问题
while (true)
{
for (int i = 1; i < 100; i++)
{
var t = new Task(() =>
{
var loh = new byte[1024 * 1024 * 1024];
});
t.Start();
}
Thread.Sleep(TimeSpan.FromSeconds(5));
}
@dudu: 我知道一般线程结束后,内存是会回收了的,我是在线程里面进行Efcore查询发现内存释放不完,肯定是什么原因导致的。。你试下在Task里面同efcore来拖一张表
@Mrs_ZMx: 我这里能重现这个问题了。
@Mrs_ZMx: 可能与线程池的线程数不够用有关,建议通过下面的代码给线程池预热试试
if (ThreadPool.SetMinThreads(200, 20))
{
Parallel.For(0, 200, a => System.Threading.Thread.Sleep(1000));
}
@Mrs_ZMx: 我这里的测试代码在内存占用达到1.2G左右的时候就会被回收
class Program
{
static void Main(string[] args)
{
if (ThreadPool.SetMinThreads(200, 20))
{
Parallel.For(0, 200, a => System.Threading.Thread.Sleep(1000));
}
for (int i = 0; i < 400; i++)
{
Task.Run(async () =>
{
var options = new DbContextOptionsBuilder<BlogDbContext>()
.UseSqlServer("server=.;database=cnblogs;integrated security=true;").Options;
using (var db = new BlogDbContext(options))
{
var d = await db.Set<BlogPost>().AsNoTracking().ToListAsync();
}
});
}
while (true)
{
Console.WriteLine("主线程的id:" + Thread.CurrentThread.ManagedThreadId + "--现在存在的线程数;" + Process.GetCurrentProcess().Threads.Count);
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}
}
@Mrs_ZMx: "我知道一般线程结束后,内存是会回收了的",如果线程不够用呢?
@dudu:没看到这段消息。。你不觉得到了1.2G其实也是很高了吗?而且即使你结束了,1.2G还在高居不下 。。
@dudu: 这种线程预热是什么原理,ThreadPool.SetMinThreads(200, 20)这个设置了,系统不会默认内置200个工作线程吗
@DHclly: 建议专门发个提问
你可以试试 GC.Collect() 强制垃圾回收
GC.Collect()试过确实有点用,但是这个强制回收会停止线程,强制GC。感觉没有找到问题的源头。刚刚开会,又花掉了下午的时间
GC.Collect()会解决问题,这也反面证明了确实是内存没有回收,但不能使用GC.Collect()来强制执行垃圾回收,因为这个方法会停掉当前线程来执行GC。明天就要演示了,有点急啊。。。
@Mrs_ZMx: 看你上面贴的代码如果一直查询的是同一张表,那最好先调用一下之前对象的Dispose(你代码中的变量d)然后再赋新的值应该会好点;
还有一个原因你这样会在短时间内创建大量线程,会占用很多内存。如果是上面的代码,你要的只是Project表的最新数据吧。这方面觉得也可以优化下。
@左眼水星:我可以(试过)休眠1秒(这一秒已经完全可以完成一个线程)开一个线程,还是会造成很多内容没GC,高达2g
顺序执行是没有任何问题的,就是多线程造成了问题
@Mrs_ZMx:
2G的话可能是缓存的查询结果,线程的内存开销不至于此
有可能是变成大对象了要不试试GC.Collect(2, GCCollectionMode.Forced, false, false);
@左眼水星:GC.Collect是能解决问题的,但这个方法是强制停掉线程来进行垃圾回收,影响性能的
仅查询数据 用下Select
就是用select
@Mrs_ZMx: 贴下实际代码噻
@通信的搞程序: 好的,实际业务是访问GRPC服务,在服务里有一个方法用databaseContext.Table.Select().ToList();随着访问量的增加内存也一直增,主要存在部分内存释放不了,后面直接堆积到了5,6个g。我后面通过测试发现是使用多线程进行efcore查询,存在一部分内存释放不了,随着线程一次一次查询,内存逐渐在堆积。具体代码是for里面开线程 new Task.StartNew(() => var t = databaseContext.Table.Select().ToList()),就这么简单
@Mrs_ZMx: https://www.cnblogs.com/weapon/p/9121143.html 看看是不是博客园发的这种问题
@通信的搞程序: 我感觉是netcore垃圾回收机制,和EFcore查询在多线程中,存在了某些问题,导致了内存泄漏。。
@通信的搞程序: 要找到源头在哪里,然后处理。。现在什么也没蹲到。欲哭无泪。。
@通信的搞程序: 我看过那篇博客,不是注入方式的问题。我直接用的using
@Mrs_ZMx: 数据库查询最好不要并发去查询同一张表吧,不然对数据库也不友好
楼主,最后怎么解决的?重启程序之后好像就可以了
楼主,最后怎么解决的?