1.问题:

.net单一服务中,大量的请求访问后台服务,多线程处理请求,但每个线程都可能出现超时的现象。记录超时日志显示,超时可能在序列化时,Socket异步发送AsyncSend数据时,普通业务处理时超时,

插入数据库时超时,而且超时时间都比较固定,内存大时可达到5s,内存小时2~3s。

2.分析问题:

超时问题,针对具体的超时现象,具体分析:

(1)插入数据库时超时,sqlserver 表被锁住,所以插入不了;

(2)序列化时超时,对象序列化成字节数组时,由于对象太大,会出现耗时的情况,因此线程被卡住,超时。

(3)Socket异步发送超时,Socket应答客户端消息,由于beginSend是异步方法,服务把字节流写入发送缓冲区,如果发送缓冲区满了,发送缓冲区没有及时发送数据,

导致字节流写不进去发送缓冲区,导致线程被挂起。

(4)处理业务时超时,业务逻辑有问题,导致超时

经过分析,以上这四种超时没办法定位到问题,测试后发现均不是以上所说的问题。

考虑到,每次请求超时,几乎所有线程都会挂起暂停,应该是有其他原因导致服务中的线程暂停了。

首先考虑原因,GC垃圾回收时,服务内存太大,垃圾自动回收,2代托管堆清理时,工作GC会把服务中其他线程都挂起。

3. .net GC回收机制

3.1 GC是如何工作的
  GC的工作流程主要分为如下几个步骤:

标记(Mark) → 计划(Plan) → 清理(Sweep) → 引用更新(Relocate) → 压缩(Compact)

GC

3.2 GC的根节点
  本文反复出现的GC的根节点也即GC Root是个什么东西呢?

每个应用程序都包含一组根(root)。每个根都是一个存储位置,其中包含指向引用类型对象的一个指针。该指针要么引用托管堆中的一个对象,要么为null。

在应用程序中,只要某对象变得不可达,也就是没有根(root)引用该对象,这个对象就会成为垃圾回收器的目标。

用一句简洁的英文描述就是:GC roots are not objects in themselves but are instead references to objects.而且,Any object referenced by a GC root will automatically survive the next garbage collection.

.NET中可以当作GC Root的对象有如下几种:

1、全局变量

2、静态变量

3、栈上的所有局部变量(JIT)

4、栈上传入的参数变量

5、寄存器中的变量

注意,只有引用类型的变量才被认为是根,值类型的变量永远不被认为是根。因为值类型存储在堆栈中,而引用类型存储在托管堆上。

3.3 什么时候发生GC
  1、当应用程序分配新的对象,GC的代的预算大小已经达到阈值,比如GC的第0代已满;

2、代码主动显式调用System.GC.Collect();

3、其他特殊情况,比如,windows报告内存不足、CLR卸载AppDomain、CLR关闭,甚至某些极端情况下系统参数设置改变也可能导致GC回收。

3.4 GC中的代
  代(Generation)引入的原因主要是为了提高性能(Performance),以避免收集整个堆(Heap)。一个基于代的垃圾回收器做出了如下几点假设:

1、对象越新,生存期越短;

2、对象越老,生存期越长;

3、回收堆的一部分,速度快于回收整个堆。

.NET的垃圾收集器将对象分为三代(Generation0,Generation1,Generation2)。不同的代里面的内容如下:

1、G0 小对象(Size<85000Byte):新分配的小于85000字节的对象。

2、G1:在GC中幸存下来的G0对象

3、G2:大对象(Size>=85000Byte);在GC中幸存下来的G1对象

object o = new Byte[85000]; //large object
Console.WriteLine(GC.GetGeneration(o)); //output is 2,not 0
3.5、当GC遇到多线程
  前面讨论的垃圾回收算法有一个很大的前提就是:只在一个线程运行。而在现实开发中,经常会出现多个线程同时访问托管堆的情况,或至少会有多个线程同时操作堆中的对象。一个线程引发垃圾回收时,其它线程绝对不能访问任何线程,因为垃圾回收器可能移动这些对象,更改它们的内存位置。CLR想要进行垃圾回收时,会立即挂起执行托管代码中的所有线程,正在执行非托管代码的线程不会挂起。然后,CLR检查每个线程的指令指针,判断线程指向到哪里。接着,指令指针与JIT生成的表进行比较,判断线程正在执行什么代码。

如果线程的指令指针恰好在一个表中标记好的偏移位置,就说明该线程抵达了一个安全点。线程可在安全点安全地挂起,直至垃圾回收结束。如果线程指令指针不在表中标记的偏移位置,则表明该线程不在安全点,CLR也就不会开始垃圾回收。在这种情况下,CLR就会劫持该线程。也就是说,CLR会修改该线程栈,使该线程指向一个CLR内部的一个特殊函数。然后,线程恢复执行。当前的方法执行完后,他就会执行这个特殊函数,这个特殊函数会将该线程安全地挂起。然而,线程有时长时间执行当前所在方法。所以,当线程恢复执行后,大约有250毫秒的时间尝试劫持线程。过了这个时间,CLR会再次挂起线程,并检查该线程的指令指针。如果线程已抵达一个安全点,垃圾回收就可以开始了。但是,如果线程还没有抵达一个安全点,CLR就检查是否调用了另一个方法。如果是,CLR再一次修改线程栈,以便从最近执行的一个方法返回之后劫持线程。然后,CLR恢复线程,进行下一次劫持尝试。所有线程都抵达安全点或被劫持之后,垃圾回收才能使用。垃圾回收完之后,所有线程都会恢复,应用程序继续运行,被劫持的线程返回最初调用它们的方法。

实际应用中,CLR大多数时候都是通过劫持线程来挂起线程,而不是根据JIT生成的表来判断线程是否到达了一个安全点。之所以如此,原因是JIT生成表需要大量内存,会增大工作集,进而严重影响性能。

这里再说一个真实案例。某web应用程序中大量使用Task,后在生产环境发生莫名其妙的现象,程序时灵时不灵,根据数据库日志(其实还可以根据Windows事件跟踪(ETW)、IIS日志以及dump文件),发现了Task执行过程中有不规律的未处理的异常,分析后怀疑是CLR垃圾回收导致,当然这种情况也只有在高并发条件下才会暴露出来。

3.6、开发中的一些建议和意见
  由于GC的代价很大,平时开发中注意一些良好的编程习惯有可能对GC有积极正面的影响,否则有可能产生不良效果。

1、尽量不要new很大的object,大对象(>=85000Byte)直接归为G2代,GC回收算法从来不对大对象堆(LOH)进行内存压缩整理,因为在堆中下移85000字节或更大的内存块会浪费太多CPU时间;

2、不要频繁的new生命周期很短object,这样频繁垃圾回收频繁压缩有可能会导致很多内存碎片,可以使用设计良好稳定运行的对象池(ObjectPool)技术来规避这种问题

3、使用更好的编程技巧,比如更好的算法、更优的数据结构、更佳的解决策略等等

update:.NET4.5.1及其以上版本已经支持压缩大对象堆,可通过System.Runtime.GCSettings.LargeObjectHeapCompactionMode进行控制实现需要压缩LOH。

4. GC模式和配置

服务器模式:

(1)给每个业务逻辑进程创建一个专用的线程,最高优先级

(2)主要应用于多处理器系统,并且作为ASP.NET Core宿主的默认配置。它会为每个处理器都创建一个GC Heap,并且会并行执行回收操作。

(3)该模式的GC可以最大化吞吐量和较好的收缩性。这种模式的特点是初始分配的内存较大,并且尽可能不回收内存,进行回收用时会很耗时,并进行内存碎片整理工作。

工作站模式:

(1)主要应用于单处理器系统,Workstation GC尽可能地通过减少垃圾回收过程中程序的暂停次数来提高性能。

(2)低负载且不常在后台(如服务)执行任务的应用程序,可以在禁用并发垃圾回收的情况下使用工作站垃圾回收。特点是会频繁回收,来阻止一次较长时间的回收。

GC Model 32-bit 64-bit
Workstation GC 16 MB 256 MB
Server GC 64 MB 4 GB
Server GC with > 4 logical CPUs 32 MB 2 GB
Server GC with > 8 logical CPUs 16 MB 1 GB

此外,CLR还会为每个处理器分配一个单独的堆。每个处理器堆里,包含一个小对象堆和大对象堆。从你的应用程序角度上看,你的代码不知道引用的对象是属于哪个堆上面的(他们都有相同的虚拟地址空间)。

使用多个堆有下一些优点

垃圾回收可以并行处理。每个GC线程处理一个对应的堆。这是的服务器模式的GC比工作站模式要快的原因。
某些情况下,分配速度会更快,尤其是将大对象相对分配在同一个堆上快。还有一些其他内部差异,比如内存段的大小,越大的段在做垃圾回收时时间也会越长。

你可以在App.config 文件里的节点里配置 服务器模式

你要如何选择工作站或者服务器模式吗?

后台GC

(1)修改后台GC配置会更改2代对象的回收策略。相对于0代和1代的回收的前台GC,它不会中断当前应用里其他的线程执行。
(2)后台GC在会而外创建一个线程用来处理2代对象的回收。这意味着,如果你同时开启后台GC和服务器GC,你将为每个处理器创建2个线程来处理GC。但这没啥大不了的,虽然进程里多了很多个线程,但这些线程在大部分时间里还是不工作的。

在你的应用执行的时候GC也可以同时进行,但在某些情况下,还是会发生阻塞。在这时,后台GC还是会将应用程序里的其它线程给挂起。
如果使用工作站模式,则始终开启后台GC模式,从.NET4.5开始,默认情况下服务器GC模式下也会开启,当然你也可以关闭它。
以下是关闭后台GC的配置

实际上,我们很少有理由去禁用后台GC。如果你想通过禁用后台GC的线程来提高你的应用程序在CPU的占用率,但这个想法是不现实的。但如果是减少GC的延迟或者频率可以考虑关闭它。

低延迟模式

如果你的应用希望在一段特定时间里高速执行,不希望被GC的2代回收打扰。你可以通过改变 GCSettings.LatencyMode 的设置来实现。

LowLatency—只能在工作站模式运行,它可以暂停2代回收。
SustainedLowLatency—可以在工作站和服务器模式下执行。它可以暂停完整的2代回收,但如果你开启里后台GC模式,你还是可以在后台GC线程里对2代对象做回收。

这两种模式都将大大的增加内存的消耗,因为它没对内存做压缩。如果你的应用需要消耗大量的内存,则最好避免开启这两个模式。

当你要准备进入低延迟模式前,最好手动执行一次完整的GC(GC.Collect(2, GCCollectionMode.Forced)。等离开低延迟模式后,也手动触发一次完成GC。
默认情况下,是不需要开启这个模式。只有你的程序执行时间不要被GC打扰才需要开启,不用在全过程都开启。举个栗子:如果你有一个股票交易的高频应用,在交易时间段里不希望发生GC回收暂停应用执行。但在股市交易结束后,你可以关闭这个模式进行完整的GC回收直到股市重新开市。

如果要开启低延迟模式,至少要符合以下标准:

在正常执行期间,完整的垃圾回收操作是不可接受的
应用程序消耗的内存要远小于可分配内存
应用程序在开启低延迟模式后,要有足够的内存撑到下一次手动执行完整回收或者重启。

这是一个很少用的配置,如果你要使用请三思而后行,因为开启之后会出现一些意想不到的后果。如果你认为还是有必要使用,请确保你的应用经过了充分测试。在开启后,系统会产生更频繁的0代和1代的回收操作,用来减少完整的回收,这可能会导致一些其他性能问题。这可能会导致解决了一个又另外产生了一个问题。

最后,请注意,低延迟模式不是一个保证。如果GC在做回收的时候仍然抛出了OutOfMemoryException异常,仍然有可能会不管你的配置选项,进行一次完整的GC回收。

5. .net memory profiler 工具跟踪

具体就不介绍了,主要是监控内存的变化和泄露,对监控GC不好

6. perfmon 性能计数器

开启性能监控器

监控指标有如下

“(PDH-CSV 4.0)                                  时间
(”,"\FLYBUS02.NET CLR Memory(Wind.IBroker.Server)# Bytes in all Heaps",     托管堆的大小         .net CLR Memory
“\FLYBUS02.NET CLR Memory(Wind.IBroker.Server)# Gen 0 Collections”,      0代回收数量          .net CLR Memory
“\FLYBUS02.NET CLR Memory(Wind.IBroker.Server)# Gen 1 Collections”,      1代回收数量          .net CLR Memory
“\FLYBUS02.NET CLR Memory(Wind.IBroker.Server)# Gen 2 Collections”,      2代回收数量          .net CLR Memory
“\FLYBUS02.NET CLR Memory(Wind.IBroker.Server)\Promoted Memory from Gen 0”,  0代–1代保留的字节大小    .net CLR Memory
“\FLYBUS02.NET CLR Memory(Wind.IBroker.Server)\Promoted Memory from Gen 1”  1代–2代保留的字节大小    .net CLR Memory
“\FLYBUS02.NET CLR Memory(Wind.IBroker.Server)\Private Bytes”          服务内存大小         Process

7 解决办法

修改GC的配置模式:

目前,由于服务器为多核16G内存的服务器,我们服务大概需要4G的内存,所有可以配置

1.服务器模式+后台GC模式(默认是conCurrent 是 true,不必配置)
在App.Config中配置

<configuration><runtime><gcConcurrent  enabled="true"/></runtime>
</configuration>
  1. 晚上请求少,创建对象不多,可以定时调用GC.Collection()对垃圾进行回收。
  2. 修改业务代码逻辑,减少创建对象。修改业务代码逻辑,减少创建对象。
  3. 尽量避免使用string,改用StringBuffer.

8. 引用文章

https://www.cnblogs.com/huchaoheng/p/6295688.html

https://blog.csdn.net/sD7O95O/article/details/78549892

https://www.cnblogs.com/yahle/p/6915751.html

.net 应用服务GC时服务挂起几秒问题及解决办法相关推荐

  1. SQL安装时出现挂起的文件操作”错误解决办法

    SQL安装时出现"以前的某个程序安装已在安装计算机上创建挂起的文件操作--"错误解决办法 打开注册表编辑器(或在命令行输入:regedit),在HKEY_LOCAL_MACHINE ...

  2. 安装Mysql时出现服务未启动(start service)解决办法

    安装Mysql时出现服务未启动(start service)解决办法 1.去控制面板卸载 2.删除Mysql卸载残留文件 3.管理员删除mysql服务 4.重新安装(注意事项!!!) 1.去控制面板卸 ...

  3. [转]Silverlight在调用wcf时传输数据过大返回Not Found的解决办法

    原文地址:http://www.cnblogs.com/gavinyao/archive/2012/04/17/2454495.html Silverlight在调用wcf时传输数据过大返回Not F ...

  4. sqlplus登录时遇到的ORA-12560: TNS: 协议适配器错误解决办法

    sqlplus登录时遇到的ORA-12560: TNS: 协议适配器错误解决办法 在windows下使用lsnrctl start启动监听之后,然后使用sqlplus登录的时候遇到了ORA-12560 ...

  5. 本地计算机无法启动wireless,win7系统使用无线时提示:“windows无法启动wireless”的解决办法...

    此文约为506字,阅读需要3分钟 如果在连接无线网络时连接不上,而系统提示了"Windows无法启动Wireless PAN DHCP Server服务(位于本地计算机上).错误1067:进 ...

  6. 在运行vue项目时发生这种 Cannot find module ‘xxxxx‘ ,解决办法?

    在运行vue项目时发生这种 Cannot find module 'xxxxx' ,解决办法? 首先,在文件夹中删除掉node_modules文件和package-lock.json文件 其次,在使用 ...

  7. win8 远程桌面时提示凭证不工作问题的终极解决办法

    原文 win8 远程桌面时提示凭证不工作问题的终极解决办法 环境说明 远程办公电脑(放置于公司.自用办公电脑.win8系统) 远程连接客户机(放置于家中.家庭日常所用.win8系统) 故障现象 最近在 ...

  8. 使用Lock and Load X 插件时导致Final Cat Pro意外退出的解决办法

    Lock and Load X是一款Mac平台上的视频稳定防抖插件,适用于FCPX.PR以及AE软件.lock and load x Mac版比PR和FCPX自带的防抖插件,功能更强大,效果更加突出, ...

  9. 【教程】关于打开一些exe文件时,打开方式为microsoft store的解决办法

    [教程]关于打开一些exe文件时,打开方式为microsoft store的解决办法 前言 解决 ---------------- 版权声明:本文为CSDN博主「SogK1997」的原创文章,遵循CC ...

最新文章

  1. 进程的同步、互斥以及PV原语
  2. 编写TA链接静态库的方法
  3. 七种常见分布式事务详解(2PC、3PC、TCC、Saga、本地事务表、MQ事务消息、最大努力通知)
  4. Eming cup Problem D. Game of numbers
  5. Maven学习之 仓库镜像
  6. 反应器(Reactor):用于事件多路分离和分派的体系结构模式
  7. [算法]海量数据问题之一
  8. 视频分割修整功哪一款视频剪辑软件更好用?
  9. ROS学习笔记(2)——ROS通信机制
  10. ECMAScript标准命名
  11. Vanishing point detection
  12. 第二章 数据查询语言DQL
  13. Win7安装typhon使用心得
  14. 注会会计-会计账户与记账方法
  15. [附源码]计算机毕业设计Python新能源汽车租赁(程序+源码+LW文档)
  16. 【PP-6】新建物料清单BOM
  17. 为什么使用vi /etc/sysconfig/network-scripts/ifcfg-ens33打开的编辑器是空的?
  18. 研发内部控制浅谈(三)(转)
  19. 大众点评 mtgisg分析
  20. 超详细anaconda安装教程(Mac,Windows,Linux版本)

热门文章

  1. 远古Vod故障404排除实战
  2. 电脑连接WiFi后浏览器无法上网但其他软件正常使用——网络代理问题解决办法
  3. 20220323-738.单调递增的数字
  4. APP 渠道推广【摘自网络】
  5. PHP-for循环初步实现日历表格思想
  6. 深入理解CNI(容器网络接口)
  7. 张江GSK 激活黑莓8820
  8. java-opencv 米粒数_opencv学习之米粒分割 #201906121549
  9. COOX基础培训之SCADA Manufacture
  10. RFID,RC522教程