Tìm, sửa và tránh rò rỉ bộ nhớ trong C # .NET: 8 cách thực hành tốt nhất

  Đánh giá    Viết đánh giá
 0      21      0
Mã tài liệu yoaduq Danh mục Ngày đăng 22/4/2019 Tác giả Lần xem 21

Bất cứ ai từng làm việc trong một dự án doanh nghiệp lớn đều biết rò rỉ bộ nhớ giống như những con chuột trong một khách sạn lớn. Bạn có thể không chú ý khi có một vài người trong số họ, nhưng bạn luôn phải cảnh giác trong trường hợp họ quá đông dân, đột nhập vào bếp và ị vào mọi thứ.

Tìm kiếm, sửa chữa và học cách tránh rò rỉ bộ nhớ là một kỹ năng quan trọng. Tôi sẽ liệt kê 8 kỹ thuật thực hành tốt nhất được sử dụng bởi tôi và các nhà phát triển .NET cao cấp đã tư vấn cho tôi cho bài viết này. Các kỹ thuật này sẽ dạy bạn phát hiện khi có sự cố rò rỉ bộ nhớ trong ứng dụng, để tìm rò rỉ bộ nhớ cụ thể và khắc phục nó. Cuối cùng, tôi sẽ bao gồm các chiến lược để theo dõi và báo cáo về rò rỉ bộ nhớ cho một chương trình được triển khai.

Xác định rò rỉ bộ nhớ trong .NET

Trong một môi trường rác được thu thập, thuật ngữ rò rỉ bộ nhớ là một chút truy cập trực quan. Làm thế nào bộ nhớ của tôi có thể bị rò rỉ khi có bộ thu gom rác ( GC ) chăm sóc để thu thập mọi thứ?

Có 2 nguyên nhân cốt lõi liên quan đến việc này. Nguyên nhân cốt lõi đầu tiên là khi bạn có các đối tượng vẫn được tham chiếu nhưng thực tế không được sử dụng. Vì chúng được tham chiếu, người thu gom rác sẽ không thu thập chúng và chúng sẽ tồn tại mãi mãi, chiếm bộ nhớ. Điều này có thể xảy ra, ví dụ, khi bạn đăng ký vào các sự kiện nhưng không bao giờ hủy đăng ký.

Nguyên nhân thứ hai là khi bạn bằng cách nào đó phân bổ bộ nhớ không được quản lý (không có bộ sưu tập rác) và không giải phóng nó. Điều này không quá khó để làm. Bản thân .NET có rất nhiều lớp phân bổ bộ nhớ không được quản lý. Hầu như bất cứ điều gì liên quan đến các luồng, đồ họa, hệ thống tệp hoặc các cuộc gọi mạng đều thực hiện điều đó dưới mui xe. Thông thường, các lớp này thực hiện một   phương thức Vứt bỏ , giải phóng bộ nhớ (chúng ta sẽ nói về điều đó sau). Bạn có thể dễ dàng phân bổ bộ nhớ không được quản lý bằng các lớp .NET đặc biệt (như Marshal) hoặc PInvoke (có một ví dụ về điều này hơn nữa).

Hãy chuyển sang danh sách kỹ thuật thực hành tốt nhất của tôi:

1. Phát hiện sự cố rò rỉ bộ nhớ với Cửa sổ công cụ chẩn đoán

Nếu bạn đến Debug | Windows | Hiển thị Công cụ chẩn đoán , bạn sẽ thấy cửa sổ này. Nếu bạn giống tôi, có lẽ bạn đã thấy cửa sổ công cụ này sau khi cài đặt Visual Studio, đóng nó ngay lập tức và không bao giờ nghĩ về nó nữa. Cửa sổ Công cụ Chẩn đoán có thể khá hữu ích mặc dù. Nó có thể dễ dàng giúp bạn phát hiện 2 vấn đề: Rò rỉ bộ nhớ và Áp lực GC .

Khi bạn có Rò rỉ bộ nhớ, biểu đồ Bộ nhớ tiến trình trông như thế này:

Bạn có thể thấy với các đường màu vàng đến từ đỉnh mà GC đang cố giải phóng bộ nhớ, nhưng nó vẫn tiếp tục tăng.

Khi bạn có áp suất GC , biểu đồ Bộ nhớ tiến trình trông như thế này:

Áp lực GC là khi bạn đang tạo các đối tượng mới và xử lý chúng quá nhanh để người thu gom rác theo kịp. Như bạn thấy trong hình, bộ nhớ gần với giới hạn của nó và các vụ nổ GC rất thường xuyên.

Bạn sẽ không thể tìm thấy rò rỉ bộ nhớ cụ thể theo cách này, nhưng bạn có thể phát hiện ra rằng bạn có vấn đề rò rỉ bộ nhớ, điều này rất hữu ích. Trong Enterprise Visual Studio, Cửa sổ Chẩn đoán cũng bao gồm một trình lược tả bộ nhớ tích hợp, cho phép tìm ra rò rỉ cụ thể. Chúng ta sẽ nói về hồ sơ bộ nhớ trong thực tiễn tốt nhất # 3.

2. Phát hiện các sự cố rò rỉ bộ nhớ với Trình quản lý tác vụ, Trình khám phá quy trình hoặc PerfMon

Cách dễ nhất thứ hai để phát hiện các sự cố rò rỉ bộ nhớ chính là với Trình quản lý tác vụ hoặc Trình khám phá quy trình (từ SysIternals). Các công cụ này có thể hiển thị số lượng bộ nhớ mà quá trình của bạn sử dụng. Nếu nó liên tục tăng theo thời gian, bạn có thể bị rò rỉ bộ nhớ.

đo lường bộ nhớ quản lý tác vụ

PerfMon khó sử dụng hơn một chút nhưng có thể hiển thị một biểu đồ đẹp về việc sử dụng bộ nhớ của bạn theo thời gian. Đây là một biểu đồ ứng dụng của tôi phân bổ bộ nhớ vô tận mà không giải phóng nó. Tôi đang sử dụng Quy trình | Bộ đếm byte riêng .

Biểu đồ byte riêng của Perfmon

Lưu ý rằng phương pháp này nổi tiếng là không đáng tin cậy. Bạn có thể tăng mức sử dụng bộ nhớ chỉ vì GC chưa thu thập được. Ngoài ra còn có vấn đề về bộ nhớ dùng chung và bộ nhớ riêng, vì vậy bạn có thể bỏ lỡ rò rỉ bộ nhớ và / hoặc chẩn đoán rò rỉ bộ nhớ không phải là của riêng bạn ( giải thích ). Cuối cùng, bạn có thể nhầm lẫn rò rỉ bộ nhớ cho áp suất GC . Trong trường hợp này, bạn không bị rò rỉ bộ nhớ nhưng bạn tạo và xử lý các đối tượng quá nhanh đến nỗi GC không theo kịp.

Mặc dù có những nhược điểm, tôi đề cập đến kỹ thuật này bởi vì nó vừa dễ sử dụng và đôi khi là công cụ duy nhất của bạn. Đó cũng là một chỉ báo tốt có gì đó không ổn khi quan sát trong một khoảng thời gian rất dài.

3. Sử dụng trình lược tả bộ nhớ để phát hiện rò rỉ bộ nhớ

Trình lược tả bộ nhớ giống như Con dao xử lý rò rỉ bộ nhớ. Đây là công cụ chính để tìm và sửa chúng. Mặc dù các kỹ thuật khác có thể dễ sử dụng hơn hoặc rẻ hơn (giấy phép profiler tốn kém), tốt nhất bạn nên thành thạo với ít nhất một trình lược tả bộ nhớ để giải quyết hiệu quả các vấn đề rò rỉ bộ nhớ.

Các tên tuổi lớn trong trình biên dịch bộ nhớ .NET là: dotMemory , Trình tạo bộ nhớ SciTech  Hồ sơ bộ nhớ ANTS . Ngoài ra còn có một trình tạo hồ sơ miễn phí trên mạng nếu bạn có Visual Studio Enterprise.

Tất cả các trình biên dịch bộ nhớ làm việc theo cách tương tự. Bạn có thể đính kèm vào một quy trình đang chạy hoặc mở tệp Dump . Trình lược tả sẽ tạo Ảnh chụp nhanh cho bộ nhớ hiện tại của tiến trình của bạn. Bạn có thể phân tích ảnh chụp nhanh theo mọi cách Ví dụ: đây là danh sách tất cả các đối tượng được phân bổ trong ảnh chụp nhanh hiện tại:

 Bộ đối tượng dotMemory

Bạn có thể xem có bao nhiêu phiên bản của mỗi loại được phân bổ, dung lượng bộ nhớ chúng và đường dẫn tham chiếu đến gốc Root .

Root Root là một đối tượng mà GC không thể giải phóng, do đó, mọi thứ mà các tham chiếu gốc của GC cũng không thể được giải phóng. Các đối tượng tĩnh và các đối tượng cục bộ Chủ đề đang hoạt động hiện tại là Rễ GC. Đọc thêm trong Tìm hiểu Bộ sưu tập rác trong .NET .

Kỹ thuật định hình nhanh nhất và hữu ích nhất là so sánh 2 ảnh chụp nhanh trong đó bộ nhớ sẽ trở về cùng trạng thái. Ảnh chụp nhanh đầu tiên được thực hiện trước một thao tác và một ảnh chụp nhanh khác được thực hiện sau thao tác. Các bước chính xác là:

  1. Bắt đầu với một số loại trạng thái nhàn rỗi trong ứng dụng của bạn. Đây có thể là Menu chính hoặc một cái gì đó tương tự.
  2. Chụp ảnh với các hồ sơ Memory bằng cách gắn-to-quá trình hoặc tiết kiệm một Dump .
  3. Chạy một hoạt động mà bạn nghi ngờ rò rỉ bộ nhớ được tạo ra. Quay trở lại trạng thái nhàn rỗi ở cuối của nó.
  4. Chụp ảnh nhanh thứ hai.
  5. So sánh cả hai ảnh chụp nhanh với hồ sơ bộ nhớ của bạn.
  6. Điều tra các trường hợp mới tạo, chúng có thể là rò rỉ bộ nhớ. Kiểm tra đường dẫn đến các Root Root và cố gắng hiểu tại sao những đối tượng đó không được giải phóng.

Đây là một video tuyệt vời trong đó 2 ảnh chụp nhanh được so sánh trong trình lược tả bộ nhớ SciTechvà tìm thấy rò rỉ bộ nhớ:

4. Sử dụng ID Tạo đối tượng ID để tìm rò rỉ bộ nhớ

Trong bài viết cuối cùng của tôi 5 Kỹ thuật để tránh rò rỉ bộ nhớ bởi các sự kiện trong C # .NET, bạn nên biết rằng tôi đã trình bày một kỹ thuật để tìm rò rỉ bộ nhớ bằng cách đặt một điểm dừng trong lớp Finalizer. Tôi sẽ chỉ cho bạn một phương pháp tương tự ở đây thậm chí còn dễ sử dụng hơn và không yêu cầu thay đổi mã. Cái này sử dụng tính năng Make Object ID của Debugger và Cửa sổ ngay lập tức.

Giả sử bạn nghi ngờ một lớp nào đó có rò rỉ bộ nhớ. Nói cách khác, bạn nghi ngờ rằng sau khi chạy một kịch bản nhất định, lớp này vẫn được tham chiếu và không bao giờ được thu thập bởi GC. Để tìm hiểu xem GC có thực sự thu thập nó hay không, hãy làm theo các bước sau:

  1. Đặt một điểm dừng trong đó thể hiện của lớp được tạo.
  2. Di chuột qua biến để mở mẹo dữ liệu của trình gỡ lỗi, sau đó nhấp chuột phải và sử dụng Make Object ID . Bạn có thể nhập vào Cửa sổ ngay lập tức $ 1 để thấy rằng ID đối tượng đã được tạo chính xác.
  3. Kết thúc kịch bản được cho là để giải phóng cá thể của bạn khỏi các tài liệu tham khảo.
  4. Buộc bộ sưu tập GC với các dòng ma thuật đã biết

5. Nhập lại $ 1 trong cửa sổ ngay lập tức. Nếu nó trả về null, thì GC đã thu thập đối tượng của bạn. Nếu không, bạn có một bộ nhớ bị rò rỉ.

Đây là tôi gỡ lỗi một kịch bản có rò rỉ bộ nhớ:

Và đây là tôi gỡ lỗi một kịch bản tương tự không bị rò rỉ bộ nhớ:

Bạn có thể buộc thu gom rác cuối cùng bằng cách nhập các dòng ma thuật trong cửa sổ ngay lập tức, làm cho kỹ thuật này trở thành một trải nghiệm gỡ lỗi hoàn toàn, không cần thay đổi mã.

Quan trọng: Cách thực hành này không hoạt động tốt trong trình gỡ lỗi .NET Core 2.X ( vấn đề ). Buộc thu gom rác trong cùng phạm vi với phân bổ đối tượng không giải phóng đối tượng đó. Bạn có thể làm điều đó với một chút nỗ lực hơn bằng cách buộc thu gom rác trong một phương thức khác ngoài phạm vi.

5. Cẩn thận với các nguồn rò rỉ bộ nhớ phổ biến

Luôn có nguy cơ gây rò rỉ bộ nhớ, nhưng có một số mẫu nhất định có nhiều khả năng làm như vậy. Tôi đề nghị hết sức cẩn thận khi sử dụng những thứ này và chủ động kiểm tra rò rỉ bộ nhớ bằng các kỹ thuật như cách thực hành tốt nhất cuối cùng.

Dưới đây là một số tội phạm phổ biến hơn:

  • Các sự kiện trong .NET nổi tiếng là gây rò rỉ bộ nhớ. Bạn có thể vô tình đăng ký một sự kiện, gây rò rỉ bộ nhớ gây tổn hại mà không hề nghi ngờ. Chủ đề này quan trọng đến nỗi tôi dành toàn bộ bài viết cho nó: 5 Kỹ thuật để tránh rò rỉ bộ nhớ bởi các sự kiện trong C # .NET bạn nên biết
  • Các biến tĩnh , các bộ sưu tập và các sự kiện tĩnh nói riêng sẽ luôn có vẻ đáng ngờ. Hãy nhớ rằng tất cả các biến tĩnh là Rễ GC , vì vậy chúng không bao giờ được thu thập bởi GC.
  •  
  • Chức năng lưu đệm - Bất kỳ loại cơ chế bộ đệm nào cũng có thể dễ dàng gây rò rỉ bộ nhớ. Bằng cách lưu trữ thông tin bộ nhớ cache trong bộ nhớ, cuối cùng, nó sẽ lấp đầy và gây ra ngoại lệ OutOfMemory . Giải pháp có thể là định kỳ xóa bộ nhớ đệm cũ hoặc giới hạn số lượng bộ nhớ đệm của bạn.
  • Ràng buộc WPF có thể nguy hiểm. Nguyên tắc chung là luôn luôn liên kết với DependencyObjecthoặcmột INotifyPropertyThay đổi vật. Khi bạn không làm như vậy, WPF sẽ tạo một tham chiếu mạnh đến nguồn ràng buộc của bạn (có nghĩa là ViewModel) từ một biến tĩnh, gây rò rỉ bộ nhớ. Thông tin thêm về rò rỉ liên kết WPF trong luồng StackOverflow hữu ích này
  • Các thành viên bị bắt - Có thể rõ ràng rằng Phương thức xử lý sự kiện có nghĩa là một đối tượng được tham chiếu, nhưng khi một biến được bắt giữ trong một phương thức ẩn danh, thì nó cũng được tham chiếu. Đây là một ví dụ về rò rỉ bộ nhớ:
  • Các chủ đề không bao giờ chấm dứt - Ngăn xếp trực tiếp của mỗi chủ đề của bạn được coi là một gốc GC . Điều này có nghĩa là cho đến khi một luồng kết thúc, mọi tham chiếu từ các biến của nó trên Stack sẽ không được thu thập bởi GC. Điều này bao gồm cả Timers là tốt. Nếu Trình xử lý dấu thời gian của bạn là một phương thức, thì đối tượng của phương thức đó được coi là tham chiếu và sẽ không được thu thập. Đây là một ví dụ về rò rỉ bộ nhớ:

Để biết thêm về chủ đề này, hãy xem bài viết của tôi 8 cách bạn có thể gây ra rò rỉ bộ nhớ trong .NET .

6. Sử dụng mẫu Vứt bỏ để tránh rò rỉ bộ nhớ không được quản lý

Ứng dụng .NET của bạn liên tục sử dụng các tài nguyên không được quản lý. Bản thân .NET framework phụ thuộc rất nhiều vào mã không được quản lý cho các hoạt động nội bộ, tối ưu hóa và API Win32. Bất cứ khi nào bạn sử dụng Luồng , Đồ họa hoặc Tệp cho Ví dụ, bạn có thể đang thực thi mã không được quản lý.

Các lớp khung .NET sử dụng mã không được quản lý thường triển khai IDis Dùng . Đó là bởi vì tài nguyên không được quản lý cần phải đượcrõ rànggiải phóng, và điều đó xảy ra trong phương pháp Vứt bỏ . Công việc duy nhất của bạn là ghi nhớ và gọi phương thức Vứt bỏ. Nếu có thể, sử dụng câu lệnh sử dụng cho điều đó.

Câu lệnh sử dụng biến đổi mã thành câu lệnh thử / cuối cùng đằng sau hậu trường, trong đó phương thức Vứt bỏ được gọi trongcuối cùng mệnh đề.

Nhưng, ngay cả khi bạn không gọi phương thức Vứt bỏ , các tài nguyên đó sẽ được giải phóng vì các lớp .NET sử dụng Mẫu Loại bỏ . Về cơ bản, điều này có nghĩa là nếu Dispose không được gọi trước đó, thì nó được gọi từ Finalizer khi đối tượng là rác được thu thập. Đó là, nếu bạn không bị rò rỉ bộ nhớ và Finalizer thực sự được gọi.

Khi bạn tự phân bổ tài nguyên không được quản lý, thì bạn chắc chắn nên sử dụng mẫu Vứt bỏ. Đây là một ví dụ:

Điểm của mẫu này là cho phép xử lý tài nguyên rõ ràng. Nhưng cũng để thêm một biện pháp bảo vệ rằng tài nguyên của bạn sẽ được xử lý trong quá trình thu gom rác (trong Bộ hoàn thiện) nếu Dispose () không được gọi.

Các GC.SuppressFinalize (this) cũng rất quan trọng. Nó đảm bảo Finalizer không được gọi trong bộ sưu tập rác nếu đối tượng đã có sẵnxử lý. Các đối tượng với Finalifier được giải phóng khác nhau và tốn kém hơn nhiều. Finalizer được thêm vào một thứ gọi là F-Reachable-Queue , giúp đối tượng tồn tại một thế hệ GC bổ sung. Ngoài ra còn có các biến chứng khác .

7. Thêm từ xa bộ nhớ từ mã

Đôi khi, bạn có thể muốn định kỳ đăng nhập sử dụng bộ nhớ của bạn. Có thể bạn nghi ngờ Máy chủ sản xuất của bạn bị rò rỉ bộ nhớ. Có lẽ bạn muốn thực hiện một số hành động khi bộ nhớ của bạn đạt đến một giới hạn nhất định. Hoặc có thể bạn đang có thói quen tốt trong việc theo dõi bộ nhớ của mình.

Có rất nhiều thông tin chúng ta có thể nhận được từ chính ứng dụng. Sử dụng bộ nhớ hiện tại đơn giản như:

Để biết thêm thông tin, bạn có thể sử dụng lớp PerformanceCorer được sử dụng cho PerfMon:

Thông tin từ bất kỳ quầy perfMon nào cũng có sẵn, rất nhiều.

Bạn có thể đi sâu hơn nữa. CLR MD (Microsoft.Diagnostics.R.78) cho phép bạn kiểm tra đống bộ nhớ hiện tại của mình và nhận bất kỳ thông tin nào có thể. Ví dụ: bạn có thể in tất cả các loại được phân bổ trong bộ nhớ, bao gồm số lượng thể hiện, đường dẫn đến gốc, v.v. Bạn khá nhiều có một hồ sơ bộ nhớ từ mã.

Để hiểu được những gì bạn có thể đạt được với CLR MD, hãy xem DumpMiner của Dudi Keleti's .

Tất cả thông tin này có thể được ghi vào một tệp, hoặc thậm chí tốt hơn, vào một công cụ từ xa như Thông tin chi tiết về ứng dụng.

8. Kiểm tra rò rỉ bộ nhớ

Đó là một thực hành tuyệt vời để chủ động kiểm tra rò rỉ bộ nhớ. Và nó không khó lắm đâu. Đây là một mẫu ngắn bạn có thể sử dụng:

Để kiểm tra chuyên sâu hơn, các trình định dạng bộ nhớ như Trình tạo bộ nhớ .NET và dotMemory của SciTech cung cấp API kiểm tra:

Tóm lược

Không biết về bạn, nhưng độ phân giải năm mới của tôi là: Quản lý bộ nhớ tốt hơn.

Bạn phải gởi bình luận/ đánh giá để thấy được link tải

Nếu bạn chưa đăng nhập xin hãy chọn ĐĂNG KÝ hoặc ĐĂNG NHẬP

BÌNH LUẬN


Nội dung bậy bạ, spam tài khoản sẽ bị khóa vĩnh viễn, IP sẽ bị khóa.
Đánh giá(nếu muốn)
 BÌNH LUẬN

ĐÁNH GIÁ


ĐIỂM TRUNG BÌNH

0
0 Đánh giá
Tài liệu rất tốt (0)
Tài liệu tốt (0)
Tài liệu rất hay (0)
Tài liệu hay (0)
Bình thường (0)

Tài liệu tương tự

TÀI LIỆU NỔI BẬT