CFSDN nhấn mạnh vào việc tạo ra giá trị thông qua mã nguồn mở. Chúng tôi cam kết xây dựng một nền tảng chia sẻ tài nguyên để mọi người làm CNTT có thể tìm thấy thế giới tuyệt vời của riêng mình tại đây.
Bài đăng trên blog CFSDN .NET API Handle Leak Analysis này được tác giả thu thập và biên soạn. Nếu bạn quan tâm đến bài viết này, vui lòng thích nó.
1. Bối cảnh
1. Kể chuyện
Tuần trước, một người bạn đến gặp tôi và nói rằng CPU và các trình xử lý của chương trình của anh ấy liên tục tăng lên mà không có dấu hiệu phục hồi. Anh ấy đã kiểm tra trong nhiều ngày nhưng không thấy tiến triển. Anh ấy đã thêm wx để tìm kiếm sự trợ giúp. Ảnh chụp màn hình như sau:


Có thể thấy người bạn này cũng rất buồn chán. Có hai vấn đề, rất khó chịu. Tôi dự định sẽ sử dụng một bài viết riêng để điều tra vấn đề sử dụng CPU cao. Bài viết này trước tiên sẽ nói về vấn đề rò rỉ tay cầm. Dù sao thì tôi đã viết hơn 20 bài viết và đây là lần đầu tiên nói về rò rỉ tay cầm. Nó khá thú vị.
2. Tay cầm là gì?
Hiểu biết cá nhân của tôi về handle là: tham chiếu đến tài nguyên lớp không được quản lý được lưu giữ trong lớp được quản lý. Với tham chiếu này, chúng ta có thể buộc tái chế tài nguyên không được quản lý. Vậy tài nguyên không được quản lý là gì? Theo hiểu biết cá nhân của tôi thì tất cả những nơi mà GC không thể tiếp cận được đều là những tài nguyên không được quản lý.
Các lớp thường chứa loại xử lý này bao gồm: FileStream, Socket, v.v. Nếu bạn có nền tảng tiên quyết này, bạn có thể sử dụng windbg để phân tích! .
.
2: phân tích windbg
1. Xem xét các triệu chứng của vấn đề
Một người bạn đã thấy handle = 8770 trong trình quản lý tác vụ, điều đó có nghĩa là có 8770 handle cho các tài nguyên không được quản lý trong chương trình. Làm thế nào để xem nó? Trước khi nói về điều này, bạn đã bao giờ gặp phải hiện tượng này chưa, tức là bất kể chương trình bị rò rỉ như thế nào, chỉ cần chúng ta thoát khỏi exe, tất cả các tài nguyên sẽ được giải phóng một cách kỳ diệu, cho dù chúng là tài nguyên được quản lý hay tài nguyên không được quản lý. Tôi tin rằng nhiều bạn tò mò về cách thực hiện điều này? ? ? Trước tiên, bạn có thể suy nghĩ về điều đó trong 10 giây.
Câu trả lời đã được tiết lộ! Nói một cách đơn giản, CLR duy trì một bảng xử lý bên trong. Khi chương trình đóng, CLR sẽ buộc tất cả các xử lý trong bảng xử lý phải được giải phóng. Khi đó, vấn đề trở nên đơn giản. Vì CLR có thể tiếp cận được nó, tôi tin rằng nó cũng có thể được thực hiện thông qua windbg, tức là thông qua lệnh !gchandles.
2. Xem bảng xử lý
Đây là lời nhắc nhở rằng phạm vi của !gchandles là AppDomain, không phải Process. Tiếp theo, hãy xem đầu ra của lệnh:
0:000> !gchandles -statStatistics: MT Count TotalSize Tên lớp...00007ffccc1d2360 3 262280 System.Byte[]00007ffccc116610 72 313224 System.Object[]00007ffccc3814a0 8246 593712 System.Threading.OverlappedDataTotal 10738 objectsHandles: Strong Handles: 312 Handles được ghim: 18 Handles được ghim không đồng bộ: 8246 Ref Count Handles: 1 Weak Long Handles: 2080 Weak Short Handles: 59 Dependent Handles: 22
Từ đầu ra, có một tập dữ liệu đặc biệt bắt mắt, đó là: Async Pinned Handles = 8246 [System.Threading.OverlappedData]. Điều này có nghĩa là gì? Từ tên tiếng Anh, chúng ta có thể thấy đây là một handle liên quan đến IO bất đồng bộ. Một số bạn nên biết rằng trong quá trình IO bất đồng bộ, một byte[] sẽ được ghim và cũng có một đối tượng ngữ cảnh IO bất đồng bộ OverlappedData.
Câu hỏi tiếp theo là: vì đây là IO không đồng bộ, vậy xử lý của nó là loại nào, như đã đề cập trước đó, là FileStream hay Socket? Để tìm câu trả lời, chúng ta cần đào sâu vào đối tượng OverlappedData. Lệnh liên quan là: !dumpheap -mt xxx & !do ... , hãy tham khảo nội dung sau:
0:000> !DumpHeap /d -mt 00007ffccc3814a0 Địa chỉ MT Kích thước000001aa2acb39c8 00007ffccc3814a0 72 000001aa2acb3fd8 00007ffccc3814a0 72 000001aa2ad323d0 00007ffccc3814a0 72 ...0:000> !do 000001aa2acb39c8Tên: System.Threading.OverlappedDataMethodBảng: 00007ffccc3814a0EELớp: 00007ffccc37ca18Kích thước: 72(0x48) byteTệp: 00007ffccc110ae8 40006b4 18 System.Object 0 thể hiện 000001aa2acb4020 _callback00007ffccc381150 40006b4 18 ...eading.Overlapped 0 thể hiện 000001aa2acb3980 _overlapped00007ffccc110ae8 40006b5 20 System.Object 0 thể hiện 000001aa2acb9fe8 _userObject00007ffccc11f130 40006b6 28 PTR 0 thể hiện 000001aa2a9bd830 _pNativeOverlapped00007ffccc11ecc0 40006b7 30 System.IntPtr 1 thể hiện 00000000000000000 _eventHandle0:000> !DumpObj /d 000001aa2acb3980Tên: System.Threading.ThreadPoolBoundHandleOverlappedMethodBảng: 00007ffccc3812a0EELớp: 00007ffccc37c9a0Kích thước: 72(0x48) byteTệp: C:\xxx\xxx\vms_210819\System.Private.CoreLib.dllFields: MT Trường Độ lệch Kiểu VT Thuộc tính Giá trị Tên00007ffccc3814a0 40006ba 8 ...ng.OverlappedData 0 thể hiện 000001aa2acb39c8 _overlappedData00007ffccc34fcd0 40006a4 10 ...completionCallback 0 thể hiện 000001aa2acb3920 _userCallback00007ffccc110ae8 40006a5 18 System.Object 0 thể hiện 000001aa2acb38c8 _userState00007ffccc380120 40006a6 20 ...locatedOverlapped 0 thể hiện 000001aa2acb3960 _preAllocated00007ffccc11f130 40006a7 30 PTR 0 thể hiện 000001aa2acb3900 _nativeOverlapped00007ffccc380eb8 40006a8 28 ...adPoolBoundHandle 0 thể hiện 000001aa2acb3900 _boundHandle00007ffccc1171c8 40006a9 38 System.Boolean 1 thể hiện 0 _completed00007ffccc34fcd0 40006a3 458 ...completionCallback 0 static 000001aa2acb4020 s_completionCallback0:000> !DumpObj /d 000001aa2acb3900Tên: System.Threading.ThreadPoolBoundHandleMethodBảng: 00007ffccc380eb8EELớp: 00007ffccc37c870Kích thước: 32(0x20) byteTệp: C:\xxx\xxx\vms_210819\System.Private.CoreLib.dllCác trường: MT Trường bù trừ Loại VT Thuộc tính Giá trị Tên00007ffccc1d76b0 40006a1 8 ...rvices.SafeHandle 0 thể hiện 000001aa2acb1d30 _handle00007ffccc1171c8 40006a2 10 System.Boolean 1 thể hiện 0 _isDisposed0:000> !DumpObj /d 000001aa2acb1d30Tên: Microsoft.Win32.SafeHandles.SafeFileHandlePhương phápBảng: 00007ffccc3807c8EELớp: 00007ffccc37c548Kích thước: 48(0x30) byteTệp: C:\xxx\xxx\xxx\System.Private.CoreLib.dllCác trường: MT Trường bù trừ Loại VT Thuộc tính Giá trị Tên00007ffccc11ecc0 4000bb4 8 System.IntPtr 1 thể hiện 0000000000000428 xử lý00007ffccc11b1e8 4000bb6 10 System.Int32 1 thể hiện 4 _state00007ffccc1171c8 4000bb6 14 System.Boolean 1 thể hiện 1 _ownsHandle00007ffccc1171c8 4000bb7 15 System.Boolean 1 thể hiện 1 _fullyInitialized00007ffccc2f1ae0 4001c3d 20 ...Private.CoreLib]] 1 thể hiện 000001aa2acb1d50 _isAsync00007ffccc380eb8 4001c3e 18 ...adPoolBoundHandle 0 thể hiện 0000000000000000 k__BackingField
00000000000000428 ở dòng thứ năm từ dưới lên là giá trị xử lý cụ thể. Tiếp theo, bạn có thể sử dụng lệnh !handle để xem thông tin cụ thể về giá trị của nó.
0:000> !handle 0000000000000428 7Handle 428Type FileAttributes 0GrantedAccess 0x100081: Đồng bộ Đọc/Danh sách,ReadAttrHandleCount 2PointerCount 65489
Từ Loại: Tệp, chúng ta có thể thấy rằng hơn 8.000 mục này là các tên tệp. . .
Có vẻ như tôi đã đi đến ngõ cụt. Mặc dù tôi đã đào bới một số thông tin, nhưng vẫn chưa đủ để tôi tìm ra nguyên nhân gốc rễ của vấn đề. Từ chuỗi tham chiếu, các đối tượng này trong gchandles nằm ở đầu chuỗi tham chiếu. Nói cách khác, tôi cần tìm một số đối tượng dữ liệu ở hạ lưu của chuỗi tham chiếu này. Một điểm vào tốt là đào bới trong đống.
3. Tìm các phần tử con của OverlappedData từ heap được quản lý
Đầu tiên chúng ta sử dụng !dumpheap -stat để kiểm tra heap được quản lý.
0:000> !dumpheap -statStatistics: MT Count TotalSize Tên lớp...00007ffccc3c5e18 939360 52604160 System.Collections.Generic.SortedSet`1+Node[[System.Collections.Generic.KeyValuePair`2[[System.String, System.Private.CoreLib],[System.String, System.Private.CoreLib]], System.Private.CoreLib]]00007ffccc1d2360 16492 69081162 System.Byte[]000001aa2a99af00 10365 76689384 Free00007ffccc1d1e18 1904987 116290870 System.String
Vì chúng ta đang tìm kiếm hạ nguồn của chuỗi tham chiếu, chúng ta nên bắt đầu với kiểu cơ bản System.String hoặc System.Byte[]. Ở đây tôi chọn kiểu trước và viết một tập lệnh để nhóm và đếm tất cả các địa chỉ dưới mt. Rốt cuộc, không thể thực hiện thủ công. Tôi đã trích xuất một vài địa chỉ từ đầu ra của tập lệnh và kiểm tra !gcroot. Tất cả chúng đều có nội dung tương tự nhau.
0:000> !gcroot 000001aa47a0c030HandleTable: 000001AA4469C090 (xử lý ghim không đồng bộ) -> 000001AA491EB908 System.Threading.OverlappedData -> 000001AA491EB8C0 System.Threading.ThreadPoolBoundHandleOverlapped -> 000001AA491EB860 System.Threading.IOCompletionCallback -> 000001AA491EAF30 System.IO.FileSystemWatcher -> 000001AA491EB458 System.IO.FileSystemEventHandler ... -> 000001AA47a0C030 System.String 000001aa2d3ea480HandleTable: 000001AA28FE9930 (tay cầm ghim không đồng bộ) -> 000001AA2DD68220 System.Threading.OverlappedData -> 000001AA2DD681D8 System.Threading.ThreadPoolBoundHandleOverlapped -> 000001AA2DD68178 System.Threading.IOCompletionCallback -> 000001AA2DD67848 System.IO.FileSystemWatcher ... -> 000001AA2DD68
Từ toàn bộ chuỗi tham chiếu, có một System.IO.FileSystemWatcher, phù hợp với handle = File được phân tích ở trên. Sau đó, các chuỗi này được xuất và hầu hết chúng đều liên quan đến appSettings, như được hiển thị bên dưới:
chuỗi: appSettings:RabbitMQLogQueuechuỗi: appSettings:MedicalMediaServerIPchuỗi: appSettings:UseHttps...
Sau đó, tôi sử dụng lệnh !strings để thực hiện phép so khớp mờ và phát hiện ra rằng có tới 61w chuỗi như vậy. . .

Đến thời điểm này, về cơ bản chúng ta có thể kết luận rằng appsettings đang được theo dõi, nhưng có vấn đề với cách thức theo dõi. . .
4. Tìm câu trả lời cuối cùng
Sau khi đưa kết quả khảo sát cho một người bạn, tôi đã hỏi anh ấy đặc biệt chú ý xem có vấn đề gì với cách xem cài đặt ứng dụng không? Vài giờ sau, cuối cùng bạn tôi cũng tìm thấy nó.


Ý nghĩa chung là: appsettings đã được theo dõi bằng cách đặt reloadOnChange=true, nhưng người viết mã không quen thuộc với khu vực này và thực hiện nhận dạng dữ liệu trên appsettings bằng cách thăm dò sau mỗi 10 giây và đây chính là nơi phát sinh vấn đề. . .
.
Ba: Tóm tắt
Trên thực tế, lý do chính cho sự cố này là tôi không quen với cách nhận biết dữ liệu mới nhất trong appsettings theo thời gian thực. Một mặt, tôi đã sử dụng chức năng giám sát reloadOnChange đi kèm với .netcore, mặt khác, tôi đã sử dụng polling để nhận biết dữ liệu. Do đó, những điều cơ bản vẫn rất quan trọng. Đừng coi đó là điều hiển nhiên! .
Đây là phần cuối của bài viết này về phân tích rò rỉ xử lý API .NET. Để biết thêm nội dung rò rỉ xử lý API .NET có liên quan, vui lòng tìm kiếm các bài viết trước đây của tôi hoặc tiếp tục duyệt các bài viết liên quan sau. Tôi hy vọng bạn sẽ ủng hộ tôi trong tương lai! .
Liên kết gốc: https://www.cnblogs.com/huangxincheng/p/15204986.html.
Cuối cùng, bài viết này về phân tích rò rỉ xử lý API .NET đã kết thúc tại đây. Nếu bạn muốn biết thêm về phân tích rò rỉ xử lý API .NET, vui lòng tìm kiếm các bài viết CFSDN hoặc tiếp tục duyệt các bài viết liên quan. Tôi hy vọng bạn sẽ ủng hộ blog của tôi trong tương lai! .
Tôi là một lập trình viên xuất sắc, rất giỏi!