目錄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 做者博客:傑哥很忙 歡迎轉載,請在明顯位置給出出處及連接