目录html
在生产环境中,部署在客户的程序在运行了将近两个月后发生了闪退。并且两个服务器的程序前后都出现了闪退现象。经过排查windows日志发现是OOM异常致使的闪退。本文记录了该异常事件完整的排查过程与解决方案。react
在本篇文章中会涉及到如下技术知识点:使用windbg对dump文件进行内存分析、使用wireshark抓包分析、powershell脚本编写、完成端口及重叠I/O原理等。linux
程序崩溃后,咱们要求客户导出一个dump文件供咱们分析,并提供程序相关的运行日志。同时查看了windows的相关日志肯定了是因为OOM(Out Of Memory)异常致使的。shell
启动windbg打开dump文件编程
因为咱们的程序是基于.net framework 3.5
开发的,所以咱们使用SOS
的相关扩展命令进行分析。须要在windbg中导入mscorwks
.loadby sos mscorwks
c#
想对windbg进行深刻学习,能够查看《使用WinDbg》讲解的很是详细。windows
经过!dumpheap -stat
对内存占用状况进行汇总统计。api
!dumpheap -stat ... 00007ff7ffbc0d50 536240 17159680 NetMQ.Core.Utils.Proactor+Item 00007ff7ffbca7f8 536242 17159744 NetMQ.Core.IOObject 00007ff7ffbcba70 536534 34338176 AsyncIO.Windows.AcceptExDelegate 00007ff7ffbcb7f0 536534 34338176 AsyncIO.Windows.ConnectExDelegate 00007ff7ffbcbdd8 1073068 60091808 AsyncIO.Windows.Overlapped 00007ff7ffbcb600 536534 90137712 AsyncIO.Windows.Socket Total 3839215 objects
因为咱们的程序底层网络通信框架时基于NetMQ自研发的框架,从内存占用状况来看全部内存占用都是NetMQ底层依赖的AsyncIO的对象。所以接下来就对具体的对象进行分析。缓存
再次经过!do
抽取几个对象查看。发现全部的对象实际已经调用过了Dispose
方法释放内存。可是对象没有被GC回收。安全
0:000> !do 00000000238b7b48 Name: AsyncIO.Windows.Overlapped MethodTable: 00007ff7ffbcbdd8 EEClass: 00007ff7ffbbea30 Size: 56(0x38) bytes (D:\FingardFC_V2.18.2\AsyncIO.dll) Fields: MT Field Offset Type VT Attr Value Name 00007ff85e5fa7f8 4000027 18 System.IntPtr 1 instance 22b0c060 m_address 00007ff85e5f3bc0 4000028 28 ...Services.GCHandle 1 instance 00000000238b7b70 m_handle 00007ff7ffbc3210 4000029 20 System.Int32 1 instance 0 <OperationType>k__BackingField 00007ff7ffbcb600 400002a 8 ...IO.Windows.Socket 0 instance 00000000238b7a68 <AsyncSocket>k__BackingField 00007ff85e5f6fc0 400002b 24 System.Boolean 1 instance 0 <InProgress>k__BackingField 00007ff85e5f6fc0 400002c 25 System.Boolean 1 instance 1 <Disposed>k__BackingField 00007ff85e5f76e0 400002d 10 System.Object 0 instance 00000000238b7df8 <State>k__BackingField 00007ff85e5ff060 4000022 58 System.Int32 1 static 40 Size 00007ff85e5ff060 4000023 5c System.Int32 1 static 8 BytesTransferredOffset 00007ff85e5ff060 4000024 60 System.Int32 1 static 16 OffsetOffset 00007ff85e5ff060 4000025 64 System.Int32 1 static 24 EventOffset 00007ff85e5ff060 4000026 68 System.Int32 1 static 32 MangerOverlappedOffset 0:000> !do 00000000238acc50 Name: AsyncIO.Windows.Overlapped MethodTable: 00007ff7ffbcbdd8 EEClass: 00007ff7ffbbea30 Size: 56(0x38) bytes (D:\FingardFC_V2.18.2\AsyncIO.dll) Fields: MT Field Offset Type VT Attr Value Name 00007ff85e5fa7f8 4000027 18 System.IntPtr 1 instance 22b0ad70 m_address 00007ff85e5f3bc0 4000028 28 ...Services.GCHandle 1 instance 00000000238acc78 m_handle 00007ff7ffbc3210 4000029 20 System.Int32 1 instance 1 <OperationType>k__BackingField 00007ff7ffbcb600 400002a 8 ...IO.Windows.Socket 0 instance 00000000238acba8 <AsyncSocket>k__BackingField 00007ff85e5f6fc0 400002b 24 System.Boolean 1 instance 1 <InProgress>k__BackingField 00007ff85e5f6fc0 400002c 25 System.Boolean 1 instance 1 <Disposed>k__BackingField 00007ff85e5f76e0 400002d 10 System.Object 0 instance 00000000238acf38 <State>k__BackingField 00007ff85e5ff060 4000022 58 System.Int32 1 static 40 Size 00007ff85e5ff060 4000023 5c System.Int32 1 static 8 BytesTransferredOffset 00007ff85e5ff060 4000024 60 System.Int32 1 static 16 OffsetOffset 00007ff85e5ff060 4000025 64 System.Int32 1 static 24 EventOffset 00007ff85e5ff060 4000026 68 System.Int32 1 static 32 MangerOverlappedOffset
查看终结队列中的对象,能够发现对象都在终结队列中。
0:000> !finq -stat Generation 0: Count Total Size Type --------------------------------------------------------- 1 168 AsyncIO.Windows.Socket 1 object, 168 bytes Generation 1: Count Total Size Type --------------------------------------------------------- 1008 169344 AsyncIO.Windows.Socket 2 48 System.Windows.Forms.VisualStyles.VisualStyleRenderer+ThemeHandle 1,010 objects, 169,392 bytes Generation 2: Count Total Size Type --------------------------------------------------------- 1 776 FC.Main.frmMain 1 104 AsyncIO.Windows.CompletionPort 535525 89968200 AsyncIO.Windows.Socket ...
查看垃圾回收器句柄的统计信息,存在大量的重叠资源对象未释放。
0:000> !gchandles GC Handle Statistics: Strong Handles: 520519 Pinned Handles: 84 Async Pinned Handles: 0 Ref Count Handles: 0 Weak Long Handles: 43 Weak Short Handles: 116 Other Handles: 0 Statistics: MT Count TotalSize Class Name ... 00007ff85e5e5be0 510 2435216 System.Object[] 00007ff7ffbcbdd8 511752 28658112 AsyncIO.Windows.Overlapped Total 520762 objects
我使用的NetMQ版本是
4.0.0.1
,使用的AsyncIO
版本是0.1.26.0
AsyncIO
重叠资源释放代码以下
public void Dispose() { if (!InProgress) { Free(); } Disposed = true; } private void Free() { Marshal.FreeHGlobal(m_address); if (m_handle.IsAllocated) { m_handle.Free(); } }
在InProgress=false
才会释放相关的非托管资源句柄。在对InProgress
查找全部引用。发现只有一个地方对其赋值为ture
public void StartOperation(OperationType operationType) { InProgress = true; Success = false; OperationType = operationType; }
再对StartOperation
查找引用,一共有4个地方调用。
能够发现该字段适用于表示重叠I/O是否正在处理。在若是重叠I/O正在处理,则不释放相关的资源,具体缘由后面讲到重叠I/O时会进行说明。
与此同时,咱们对程序日志也进行了分析。发现咱们的程序接收到了大量的Http请求。
因为咱们和客户接口是经过TCP协议传输,而非HTTP协议,所以理论上不该该会有HTTP请求发到咱们程序端口上。又由于咱们程序有接收超时机制,即便有咱们没法解析的无效请求,超过了超时时间咱们也会将对应的资源释放。并且从dump文件来看也没有咱们未释放的资源对象。
为了搞清楚究竟是什么请求发到咱们程序上,所以要求客户在服务器抓包。咱们对抓包文件进行分析。发现抓到了大量的异常链接,每5秒会有2个。
而后我经过计算未释放对象的数量基本与接收到这个包数量吻合。所以初步判定内存泄漏是因为该包引发的。这个包应该是一个服务监控程序发的,每五秒发一次,有2个地址在往咱们程序发。
肯定了初步的缘由,接下来就须要进行源码分析,排查问题点。因为AsyncIO
使用的是基于完成端口的重叠I/O,所以有必要先对重叠I/O和完成端口进行简单介绍。
通常来讲咱们开发程序须要进行I/O读写使用同步I/O与异步I/O两种方式。
同步I/O是大多数开发人员习惯的使用方式,从文件或网络中读取数据,线程会被挂起,等待数据读取完毕后继续执行。异步I/O则不会等待I/O调用完成,而是当即发返回,操做系统完成咱们的I/O请求后会进行通知。
在Windows下的异步I/O咱们也能够称之为重叠(overlapped)I/O。重叠的意思是执行I/O请求的时间与线程执行其余任务的时间是重叠的,即执行真正I/O请求的时候,咱们的工做线程能够执行其余请求,而不会阻塞等待I/O请求执行完毕。
实际在windows上一共支持四种接收完成通知的方式。分别为触发设备内核对象、触发时间内核对象、可提醒I/O以及I/O完成端口。其余三种有或多或少的缺点,而完成端口则是在Windows上性能最佳的接收I/O完成通知的方式。
想要详细了解四种接收完成通知方式的同窗能够查阅《Windows via C/C++ 第五版》(也被称为Windows核心编程第五版)的第十章-同步设备I/O与异步设备I/O的10.5节。
I/O完成端口的设计理论依据是并发编程的线程数必须有一个上限,即最佳并发线程数为CPU的逻辑线程数。I/O完成端口充分的发挥了并发编程的优点的同时又避免了线程上下文切换带来的性能损失。
在大多数x86和x64的多处理器,线程上下文切换时间间隔大约为15ms。
CPU每过大约15ms将CPU寄存器当前的线程上下文存回到该线程的上下文,而后该线程不在运行。而后系统检查剩下的可调度线程内核对象,选择一个线程的内核对象,将其上下文载入导CPU寄存器中。
关于Windows线程相关内容能够查阅《Windows via C/C++ 第五版》的第七章
目前常提到的I/O多路复用主要包含两种线程模型,Reactor模型和Procator模型。
Reactor模型是同步非阻塞线程模型。在设备可读写时,系统会进行通知,而后咱们从设备读写数据。
Proactor模型时异步线程模型。在读写完毕时,系统会进行通知,而后咱们就能够处理读写完毕后的事件。
在windows的完成端口就是系统层面的异步I/O模型。而linux仅支持select、epoll、kqueue等同步非阻塞I/O模型。
关于Reactor和Proactor的具体处理逻辑能够看Reactor与Proactor的概念和如何深入理解reactor和proactor?两篇文章。
为了更好的分析问题,还须要清楚重叠I/O和完成端口的完整处理流程。
I/O设备包含了如文件、目录、套接字、逻辑/物理磁盘驱动器等等。因为windows下异步I/O设计的通用性,因此I/O设备都能充分利用重叠I/O和完成端口提高性能。因为目前咱们的场景是使用套接字(socket)进行I/O读写,所以后面直接使用套接字来表示设备,实际其余I/O的处理流程也是同样的。
在外面建立网络监听的时候,首先咱们须要建立一个完成端口,后续设备的通知都须要经过该完成端口进行通知。
建立完成端口的时候能够指定容许并发执行线程的数量,在应用程序初始化时,就会建立线程池,并初始化线程,以便提升应用程序的性能。
相比同步I/O,使用完成端口须要咱们先将设备注册到完成端口。
首先咱们建立一个用于监听的套接字,而后将其绑定到完成端口上。该操做会将套接字添加到完成端口的设备列表中,这样当该套接字的I/O请求处理完成时,I/O线程就会将该套接字的完成事件加入到完成端口的I/O完成队列中。
注册完以后就能够绑定并开始监听端口了。
同步I/O是在设备可读写的时候会通知咱们,而后在建立一个套接字用于处理客户端I/O读写。
异步I/O则须要先建立一个套接字,而后将其绑定到完成端口上,当咱们接收到新的客户端请求时,实际的I/O操做已经完成。
因为建立套接字的开销很是大,所以异步I/O提早准备好一个套接字相比同步I/O接收到请求之后再建立,性能会更好。
同步I/O能够断的查看设备是否可读。当设备可读时,再从设备缓冲区读取数据到内存中。
异步I/O首先须要初始化一个内存空间用于接收数据,而后调用重叠读操做,当系统接收到数据时,I/O线程将数据直接写入到咱们提供的内存地址中,完成后就会将I/O请求加入I/O完成队列,咱们就能够接收到I/O读完成通知。当咱们收到通知时,若是没有发生错误,实际数据已经从系统缓冲取加载到内存了。
同步I/O在发送数据的时候同步的将数据写入到缓冲区。这个过程咱们的线程实际是阻塞的。
异步I/O在发送数据的时候,先发起重叠写操做,当数据写入到缓冲区后,就会将I/O请求加入到I/O完成队列。咱们就能够收到I/O写完成的通知。因此实际数据写入缓冲区时咱们的工做线程仍然能够并发处理其余事情。
根据WSK_SEND文档描述,WSK子系统在经过套接字发送数据时不执行任何数据缓冲。所以,在实际发送全部数据以前,WSK子系统不会完成对WskSend函数的调用。根据我的对该描述的理解,异步I/O发生请求接收到完成通知时,数据应该已经成功发送到对端。若是有谁能有明确的结论,麻烦告知我一下。
在简单介绍了重叠I/O和完成端口后,回到问题排查中。因为前面咱们已经发现全部内存泄漏点都是因为重叠资源未释放致使的,而实际咱们已经调用过Dipose
释放资源
首先来看下建立套接字、接收数据、发送数据和释放套接字的时候分别作了什么
public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) : base(addressFamily, socketType, protocolType) { m_disposed = false; m_inOverlapped = new Overlapped(this); m_outOverlapped = new Overlapped(this); m_sendWSABuffer = new WSABuffer(); m_receiveWSABuffer = new WSABuffer(); InitSocket(); InitDynamicMethods(); }
public Overlapped(Windows.Socket asyncSocket) { Disposed = false; InProgress = false; AsyncSocket = asyncSocket; m_address = Marshal.AllocHGlobal(Size); Marshal.WriteIntPtr(m_address, IntPtr.Zero); Marshal.WriteIntPtr(m_address,BytesTransferredOffset, IntPtr.Zero); Marshal.WriteInt64(m_address, OffsetOffset, 0); Marshal.WriteIntPtr(m_address, EventOffset, IntPtr.Zero); m_handle = GCHandle.Alloc(this, GCHandleType.Normal); Marshal.WriteIntPtr(m_address, MangerOverlappedOffset, GCHandle.ToIntPtr(m_handle)); }
GCHandle.Alloc
分配句柄,防止托管对象被GC回收致使非托管资源被回收。只有调用Free
才能被回收。WSABuffer
。当发送或接收数据时会直接使用该对象地址,而不会发生内存复制。private void InitSocket() { Handle = UnsafeMethods.WSASocket(AddressFamily, SocketType, ProtocolType, IntPtr.Zero, 0, SocketConstructorFlags.WSA_FLAG_OVERLAPPED); if (Handle == UnsafeMethods.INVALID_HANDLE_VALUE) { throw new SocketException(); } }
internal static class UnsafeMethods { public static readonly Guid WSAID_CONNECTEX = new Guid("25a207b9-ddf3-4660-8ee9-76e58c74063e"); public static readonly Guid WSAID_ACCEPT_EX = new Guid("b5367df1-cbac-11cf-95ca-00805f48a192"); ... }
private void InitDynamicMethods() { m_connectEx = (ConnectExDelegate)LoadDynamicMethod<ConnectExDelegate>(UnsafeMethods.WSAID_CONNECTEX); m_acceptEx = (AcceptExDelegate)LoadDynamicMethod<AcceptExDelegate>(UnsafeMethods.WSAID_ACCEPT_EX); }
public void AcceptInternal(AsyncSocket socket) { if (m_acceptSocketBufferAddress == IntPtr.Zero) { m_acceptSocketBufferSize = (m_boundAddress.Size + 16) * 2; m_acceptSocketBufferAddress = Marshal.AllocHGlobal(m_acceptSocketBufferSize); } int bytesReceived; m_acceptSocket = socket as Windows.Socket; m_inOverlapped.StartOperation(OperationType.Accept); if (!m_acceptEx(Handle, m_acceptSocket.Handle, m_acceptSocketBufferAddress, 0, m_acceptSocketBufferSize / 2, m_acceptSocketBufferSize / 2, out bytesReceived, m_inOverlapped.Address)) { var socketError = (SocketError)Marshal.GetLastWin32Error(); if (socketError != SocketError.IOPending) { throw new SocketException((int)socketError); } } else { CompletionPort.PostCompletionStatus(m_inOverlapped.Address); } }
m_boundAddress
是当前监听的套接字对象。m_boundAddress
,m_boundAddress.Size
则是根据IPV4仍是IPV6决定的,具体细节不作分析。经过Marshal.AllocHGlobal
分配非托管内存,返回一个地址。m_acceptEx
异步接收客户链接。前面提到异步I/O接收,先建立套接字用于接收,这样真正到接收客户端链接时就无需再建立套接字了。GetLastWin32Error
判断操做是否执行成功。
public override void Receive(byte[] buffer, int offset, int count, SocketFlags flags) { if (buffer == null) throw new ArgumentNullException("buffer"); if (m_receivePinnedBuffer == null) { m_receivePinnedBuffer = new PinnedBuffer(buffer); } else if (m_receivePinnedBuffer.Buffer != buffer) { m_receivePinnedBuffer.Switch(buffer); } m_receiveWSABuffer.Pointer = new IntPtr(m_receivePinnedBuffer.Address + offset); m_receiveWSABuffer.Length = count; m_inOverlapped.StartOperation(OperationType.Receive); int bytesTransferred; SocketError socketError = UnsafeMethods.WSARecv(Handle, ref m_receiveWSABuffer, 1, out bytesTransferred, ref flags, m_inOverlapped.Address, IntPtr.Zero); if (socketError != SocketError.Success) { socketError = (SocketError)Marshal.GetLastWin32Error(); if (socketError != SocketError.IOPending) { throw new SocketException((int)socketError); } } }
接收时首先将接收数据转换为WSABuffer
对象。因为异步I/O请求完成以前,必定不能移动或销毁所使用的数据缓存和重叠接口,所以咱们须要将数据缓存钉住,防止它被垃圾回收,且防止垃圾回收内存整理时对象被移动致使地址发生变化。
class PinnedBuffer : IDisposable { private GCHandle m_handle; public PinnedBuffer(byte[] buffer) { SetBuffer(buffer); } public byte[] Buffer { get; private set; } public Int64 Address { get; private set; } public void Switch(byte[] buffer) { m_handle.Free(); SetBuffer(buffer); } private void SetBuffer(byte[] buffer) { Buffer = buffer; m_handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); Address = Marshal.UnsafeAddrOfPinnedArrayElement(Buffer, 0).ToInt64(); } public void Dispose() { m_handle.Free(); Buffer = null; Address = 0; } }
因为咱们传递的值数据缓存地址,所以异步I/O不会发生内存复制,提升了性能。
当标记了Pinned或Normal,GC都不会回收资源,可是标记为Normal时因为垃圾回收内存整理地址可能会变,而Pinned则表示该对象不要移动。这样就保证了重叠操做不会发生错误。
所以在重叠操做处理的时候,咱们经过m_inOverlapped.StartOperation(OperationType.Receive);
设置重叠对象的InProgress
属性为true,表示重叠操做正在处理中。
发送数据和接收数据相似,这里不作具体说明。下面将与接收数据不一样的代码列出来。
public override void Send(byte[] buffer, int offset, int count, SocketFlags flags) { ... m_sendWSABuffer.Pointer = new IntPtr(m_sendPinnedBuffer.Address + offset); m_sendWSABuffer.Length = count; m_outOverlapped.StartOperation(OperationType.Send); int bytesTransferred; SocketError socketError = UnsafeMethods.WSASend(Handle, ref m_sendWSABuffer, 1, out bytesTransferred, flags, m_outOverlapped.Address, IntPtr.Zero); ... }
当网络传输完成时,须要释放套接字,同时还须要释放相关的非托管资源。
private void Dispose(bool disposing) { if (!m_disposed) { m_disposed = true; m_inOverlapped.Dispose(); m_outOverlapped.Dispose(); // for Windows XP #if NETSTANDARD1_3 UnsafeMethods.CancelIoEx(Handle, IntPtr.Zero); #else if (Environment.OSVersion.Version.Major == 5) UnsafeMethods.CancelIo(Handle); else UnsafeMethods.CancelIoEx(Handle, IntPtr.Zero); #endif int error = UnsafeMethods.closesocket(Handle); if (error != 0) { error = Marshal.GetLastWin32Error(); } ... if (m_acceptSocket != null) m_acceptSocket.Dispose(); } }
释放套接字资源的时候首先须要释放相关的重叠资源。前面已经看过释放重叠资源的代码,这里为了方便分析,再次列一下。
public void Dispose() { if (!InProgress) { Free(); } Disposed = true; } private void Free() { Marshal.FreeHGlobal(m_address); if (m_handle.IsAllocated) { m_handle.Free(); } }
前面详细的介绍和分析了异步(重叠)I/O和完成端口的缘由,那么接下来对内存泄露的具体缘由进行分析。咱们经过dump文件已经知道了套接字对象实际已经被释放了。套接字对象和重叠资源对象造成了循环引用,可是GC是很是聪明的,可以识别这种状况,仍然是能够将其回收掉。可是为何套接字对象和重叠资源仍是没有被回收掉呢?
这是由于因为咱们的重叠操做正在处理,所以InProgress
设置成了true,可是因为释放重叠资源的时候重叠操做正在处理,所以咱们不能经过Free
释放重叠资源的句柄。而是要等重叠操做成后才能释放。而以后就没有在收到I/O完成通知。那么分析如下没有I/O完成通知的可能状况有如下:
GetLastError
将会返回ERROR_OPERATION_ABORTED其余错误。
须要注意的是,若异步I/O操做已经待处理,此时取消操做将会进入到I/O完成队列。所以若取消I/O操做后重叠资源能够被安全释放。
处理I/O完成操做事件的代码以下
private void HandleCompletionStatus(out CompletionStatus completionStatus, IntPtr overlappedAddress, IntPtr completionKey, int bytesTransferred) { ... var overlapped = Overlapped.CompleteOperation(overlappedAddress); ... }
在处理完成事件时,会判断当前重叠资源是否已经释放,若已经释放则将相关句柄释放掉,此时就能够被GC回收。
public static Overlapped CompleteOperation(IntPtr overlappedAddress) { IntPtr managedOverlapped = Marshal.ReadIntPtr(overlappedAddress, MangerOverlappedOffset); GCHandle handle = GCHandle.FromIntPtr(managedOverlapped); Overlapped overlapped = (Overlapped) handle.Target; overlapped.Complete(); if (overlapped.Disposed) { overlapped.Free(); overlapped.Success = false; } else { overlapped.Success = Marshal.ReadIntPtr(overlapped.m_address).Equals(IntPtr.Zero); } return overlapped; }
以接收数据为例,能够对问题的缘由进行确认。
当咱们调用重叠操做的时候。若重叠操做返回的结果是SUCCESS和ERROR_IO_PENDING之外的值,则重叠操做并无被真正的提交。就如咱们前面所将,重叠操做提交到设备驱动队列时会返回ERROR_IO_PENDING,而以同步方式执行完成时则直接返回SUCCESS。
在发生和接收时判断如下返回结果的若不是SUCCESS和ERROR_IO_PENDING,则经过m_outOverlapped.Complete();
设置InProgress
对象值为true。这样在释放资源的时候就直接将重叠资源释放掉。
public override void Send(byte[] buffer, int offset, int count, SocketFlags flags) { ... m_outOverlapped.StartOperation(OperationType.Send); int bytesTransferred; SocketError socketError = UnsafeMethods.WSASend(Handle, ref m_sendWSABuffer, 1, out bytesTransferred, flags, m_outOverlapped.Address, IntPtr.Zero); if (socketError != SocketError.Success) { socketError = (SocketError)Marshal.GetLastWin32Error(); if (socketError != SocketError.IOPending) { m_outOverlapped.Complete(); throw new SocketException((int)socketError); } } } public override void Receive(byte[] buffer, int offset, int count, SocketFlags flags) { ... m_inOverlapped.StartOperation(OperationType.Receive); int bytesTransferred; SocketError socketError = UnsafeMethods.WSARecv(Handle, ref m_receiveWSABuffer, 1, out bytesTransferred, ref flags, m_inOverlapped.Address, IntPtr.Zero); if (socketError != SocketError.Success) { socketError = (SocketError)Marshal.GetLastWin32Error(); if (socketError != SocketError.IOPending) { m_outOverlapped.Complete(); throw new SocketException((int)socketError); } } }
因为这并非必现的,所以写一个脚本发生大量的链接后客户立刻重置的包进行重现及验证是否解决。
RSTTEST.ps1
内容以下,在建立了socket以后不要正常关闭,采用exit退出的方式,让GC直接回收对象。
$endpoint = "127.0.0.1" $port =12345 $IP = [System.Net.Dns]::GetHostAddresses($EndPoint) $Address = [System.Net.IPAddress]::Parse($IP) $Socket = New-Object System.Net.Sockets.TCPClient($Address,$Port) exit
MUTIRSTTEST.ps1
,经过调用屡次RSTTEST.ps1达到不断的发生异常链接包。
param([int]$count,[string]$path) $command = (Join-Path $path RSTTEST.ps1) for($i = 1;$i -le $count;$i++ ){ powershell . $command Write-Host $i }
本文记录了一次真实生产环境的内存泄漏事件进行分析过程。最终经过内存分析、抓包分析、源码分析等方式肯定了最终问题产生的缘由。在本次分析中对于非托管资源释放、重叠I/O和完成端口进行了深刻的学习。
本文地址:http://www.javashuo.com/article/p-xsspoxda-kx.html 做者博客:杰哥很忙 欢迎转载,请在明显位置给出出处及连接