8 cách bạn có thể gây rò rỉ bộ nhớ trong .NET

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

Bất kỳ nhà phát triển .NET có kinh nghiệm nào cũng biết rằng mặc dù các ứng dụng .NET có trình thu gom rác, nhưng rò rỉ bộ nhớ luôn xảy ra. Không phải là trình thu gom rác có lỗi, chỉ là có những cách chúng ta có thể (dễ dàng) gây rò rỉ bộ nhớ trong một ngôn ngữ được quản lý.

Rò rỉ bộ nhớ là những sinh vật xấu lén lút. Thật dễ dàng để bỏ qua chúng trong một thời gian rất dài, trong khi chúng dần phá hủy ứng dụng. Với rò rỉ bộ nhớ, mức tiêu thụ bộ nhớ của bạn tăng lên, tạo ra các vấn đề về áp suất và hiệu suất của GC. Cuối cùng, chương trình sẽ gặp sự cố với ngoại lệ hết bộ nhớ.

Trong bài viết này, chúng tôi sẽ đề cập đến những lý do phổ biến nhất cho rò rỉ bộ nhớ trong các chương trình .NET. Tất cả các ví dụ đều bằng C #, nhưng chúng có liên quan đến các ngôn ngữ khác.

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

Trong một môi trường được thu gom rác, thuật ngữ rò rỉ bộ nhớ là một chút 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, nên GC 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ý. Hãy gọi đây là rò rỉ bộ nhớ được quản lý .

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 Disposephương thức, giải phóng bộ nhớ. 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 với PInvoke .

Nhiều người chia sẻ ý kiến ​​rằng rò rỉ bộ nhớ được quản lý hoàn toàn không phải là rò rỉ bộ nhớ vì chúng vẫn được tham chiếu và về mặt lý thuyết có thể được phân bổ lại. Đó là một vấn đề về định nghĩa và quan điểm của tôi là chúng thực sự bị rò rỉ bộ nhớ. Chúng giữ bộ nhớ không thể được cấp phát cho một thể hiện khác và cuối cùng sẽ gây ra ngoại lệ hết bộ nhớ. Đối với bài viết này, tôi sẽ giải quyết cả rò rỉ bộ nhớ được quản lý và rò rỉ bộ nhớ không được quản lý, cũng như rò rỉ bộ nhớ.

Dưới đây là 8 trong số những người phạm tội phổ biến nhất. 6 đầu tiên đề cập đến rò rỉ bộ nhớ được quản lý và 2 cuối cùng về rò rỉ bộ nhớ không được quản lý:

1. Đăng ký tham gia sự kiện

Các sự kiện trong .NET nổi tiếng là gây rò rỉ bộ nhớ. Lý do rất đơn giản: Một khi bạn đăng ký vào một sự kiện, đối tượng đó sẽ giữ một tham chiếu đến lớp của bạn. Đó là trừ khi bạn đăng ký với một phương thức ẩn danh mà không bắt được một thành viên lớp. Xem xét ví dụ này:

Giả sử các wifiManageroutlives MyClass, bạn có một rò rỉ bộ nhớ trên tay của bạn. Bất kỳ trường hợp nào MyClassđược tham chiếu bởi wifiManagervà sẽ không bao giờ được phân bổ bởi người thu gom rác.

Sự kiện thực sự nguy hiểm và tôi đã viết toàn bộ bài viết về nó có tên 5 Kỹ thuật để tránh Rò rỉ bộ nhớ bởi các sự kiện trong C # .NET mà bạn nên biết .

vậy, bạn có thể làm gì? Có một số mô hình tuyệt vời để ngăn chặn rò rỉ bộ nhớ từ sự kiện trong bài viếtđược đề cập . Không đi sâu vào chi tiết, một số trong số đó là:

  1. Hủy đăng ký từ sự kiện này.
  2. Sử dụng các mẫu xử lý yếu.
  3. Đăng ký nếu có thể với một chức năng ẩn danh và không bắt giữ bất kỳ thành viên.

2. Bắt các thành viên trong các phương thức ẩn danh

Mặc dù có thể rõ ràng là một phương thức xử lý sự kiện có nghĩa là một đối tượng được tham chiếu, nhưng ít rõ ràng hơn là áp dụng tương tự khi một thành viên lớp bị bắt trong một phương thức ẩn danh.

Đây là một ví dụ:

Trong mã này, thành viên _idđược bắt giữ trong phương thức ẩn danh và kết quả là cá thể cũng được tham chiếu. Điều này có nghĩa là trong khi JobQueuetồn tại và các tham chiếu mà đại biểu công việc, nó cũng sẽ tham chiếu một thể hiện của MyClass.

Giải pháp có thể khá đơn giản - gán một biến cục bộ:

Bằng cách gán giá trị cho một biến cục bộ, không có gì được ghi lại và bạn đã tránh được rò rỉ bộ nhớ tiềm năng.

3. Biến tĩnh

Một số nhà phát triển tôi biết xem xét sử dụng các biến tĩnh luôn là một thực tiễn xấu. Mặc dù đó là một chút cực đoan, có một điểm nhất định với nó khi nói về rò rỉ bộ nhớ.

Hãy xem xét cách thức hoạt động của công cụ thu gom rác. Ý tưởng cơ bản là GC đi qua tất cả các đối tượng gốc của GC và đánh dấu chúng là không thu thập được. Sau đó, GC đi đến tất cả các đối tượng mà họ tham chiếu và đánh dấu là không thu thập là tốt. Và như vậy. Cuối cùng, GC thu thập mọi thứ còn lại ( bài viết tuyệt vời về bộ sưu tập rác).

Vì vậy, những gì được coi là một gốc Root ?

  1. Live Stack của các chủ đề đang chạy.
  2. Biến tĩnh.
  3. Các đối tượng được quản lý được chuyển đến các đối tượng COM bằng cách xen kẽ (Phân bổ bộ nhớ sẽ được thực hiện theo số tham chiếu)

Điều này có nghĩa là các biến tĩnh và mọi thứ chúng tham chiếu sẽ không bao giờ được thu gom rác. Đây là một ví dụ:

Nếu vì bất kỳ lý do gì, bạn quyết định viết mã ở trên, bất kỳ trường hợp nào MyClasssẽ tồn tại mãi trong bộ nhớ, gây rò rỉ bộ nhớ.

4. Chức năng bộ nhớ đệm

Các nhà phát triển yêu thích bộ nhớ đệm. Tại sao phải thực hiện thao tác hai lần khi bạn có thể thực hiện một lần và lưu kết quả, phải không?

Điều đó đủ đúng, nhưng nếu bạn lưu trữ bộ nhớ cache vô thời hạn, cuối cùng bạn sẽ hết bộ nhớ. Xem xét ví dụ này:

Đoạn mã này có thể tiết kiệm một số chuyến đi đắt tiền vào cơ sở dữ liệu, nhưng giá cả đang làm xáo trộn bộ nhớ của bạn.

Bạn có thể làm một số điều để giải quyết điều này:

  1. Xóa bộ nhớ đệm không được sử dụng trong một thời gian
  2. Giới hạn kích thước bộ nhớ đệm
  3. Sử dụng WeakReferenceđể giữ các đối tượng lưu trữ. Điều này phụ thuộc vào trình thu gom rác để quyết định khi nào nên xóa bộ đệm, nhưng có thể không phải là một ý tưởng tồi. GC sẽ thúc đẩy các đối tượng vẫn còn được sử dụng cho các thế hệ cao hơn để giữ chúng lâu hơn. Điều đó có nghĩa là các đối tượng được sử dụng thường xuyên sẽ lưu trong bộ nhớ cache lâu hơn.

5. Ràng buộc WPF không chính xác

WPF Bindings thực sự có thể gây rò rỉ bộ nhớ. Nguyên tắc chung là luôn luôn liên kết với một DependencyObjecthoặc một INotifyPropertyChangedđối tượng. 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ớ ( giải thích ).

Đây là một ví dụ.

Mô hình xem này sẽ ở trong bộ nhớ mãi mãi:

Trong khi Mô hình xem này sẽ không gây rò rỉ bộ nhớ:

Nó thực sự không quan trọng nếu bạn gọi PropertyChangedhay không, điều quan trọng là lớp xuất phát từ đó INotifyPropertyChanged. Điều này nói với cơ sở hạ tầng WPF không tạo ra một tài liệu tham khảo mạnh mẽ.

Rò rỉ bộ nhớ xảy ra khi chế độ liên kết là OneWay hoặc TwoWay. Nếu liên kết là OneTime hoặc OneWayToSource, thì đó không phải là vấn đề.

Một vấn đề rò rỉ bộ nhớ WPF khác xảy ra khi liên kết với một bộ sưu tập. Nếu bộ sưu tập đó không thực hiện INotifyCollectionChanged, thì bạn sẽ bị rò rỉ bộ nhớ. Bạn có thể tránh vấn đề bằng cách sử dụng ObservableCollectiongiao diện đó.

6. Chủ đề không bao giờ chấm dứt

Chúng ta đã nói về cách thức hoạt động của GC và về gốc rễ của GC . Tôi đã đề cập rằng Live Stackđược coi là một gốc GC. Live Stack bao gồm tất cả các biến cục bộ và các thành viên của ngăn xếp cuộc gọi trong các luồng đang chạy.

Nếu vì bất kỳ lý do gì, bạn đã tạo ra một luồng chạy vô tận mà không có gì và có các tham chiếu đến các đối tượng, đó sẽ là một rò rỉ bộ nhớ. Một ví dụ về cách điều này có thể dễ dàng xảy ra là với a Timer. Hãy xem xét mã này:

Nếu bạn không thực sự dừng bộ hẹn giờ, nó sẽ chạy trong một luồng riêng biệt, tham chiếu một thể hiện của MyClass, ngăn không cho nó được thu thập.

7. Không phân bổ bộ nhớ không được quản lý

Cho đến nay, chúng tôi chỉ nói về bộ nhớ được quản lý . Đó là, bộ nhớ được quản lý bởi người thu gom rác. Bộ nhớ không được quản lý là một vấn đề hoàn toàn khác - Thay vì chỉ tránh các tham chiếu không cần thiết, bạn sẽ cần phân bổ lại bộ nhớ một cách rõ ràng.

Đây là một ví dụ đơn giản:

Trong phương pháp trên, chúng tôi đã sử dụng Marshal.AllocHGlobal, phân bổ bộ đệm của bộ nhớ không được quản lý ( tài liệu ). Trong mui xe, AllocHGlobalgọi LocalAllochàm trong Kernel32.dll . Nếu không giải phóng rõ ràng tay cầm với Marshal.FreeHGlobal, bộ nhớ đệm đó sẽ được coi là được lấy trong đống bộ nhớ của tiến trình, gây rò rỉ bộ nhớ.

Để giải quyết các vấn đề như vậy, bạn có thể thêm một Disposephương thức giải phóng mọi tài nguyên không được quản lý, như vậy:

Rò rỉ bộ nhớ không được quản lý theo cách tồi tệ nhất so với rò rỉ bộ nhớ được quản lý do cácvấn đề phân mảnh bộ nhớ . Bộ nhớ được quản lý có thể được di chuyển xung quanh bởi bộ thu gom rác, tạo không gian cho các đối tượng khác. Bộ nhớ không được quản lý, tuy nhiên, mãi mãi bị mắc kẹt tại chỗ.

8. Thêm Vứt bỏ mà không gọi nó

Trong ví dụ trước, chúng tôi đã thêm Disposephương thức để giải phóng mọi tài nguyên không được quản lý. Điều đó thật tuyệt, nhưng điều gì xảy ra khi bất cứ ai sử dụng lớp học không gọi Dispose?

Một điều bạn có thể làm là sử dụng usingcâu lệnh trong C #:

Điều này hoạt động trên IDisposablecác lớp và dịch bởi trình biên dịch sang đây:

Điều này rất hữu ích vì ngay cả khi một ngoại lệ được ném, Disposevẫn sẽ được gọi.

Một điều khác bạn có thể làm là sử dụng Mô hình Vứt bỏ . Đây là một ví dụ về cách bạn sẽ thực hiện nó:

Mẫu này đảm bảo rằng ngay cả khi Disposekhông được gọi, thì cuối cùng nó sẽ được gọi khi thể hiện là rác được thu thập. Mặt khác, nếu Dispose được gọi, thì bộ hoàn thiện bị triệt tiêu. Việc loại bỏ bộ hoàn thiện rất quan trọng vì bộ hoàn thiện rất tốn kém và có thể gây ra các vấn đề về hiệu suất.

Tuy nhiên, kiểu vứt bỏ không chống đạn. Nếu Disposekhông bao giờ được gọi  lớp của bạn không được thu gom rác do rò rỉ bộ nhớ được quản lý , thì tài nguyên không được quản lý sẽ không được giải phóng.

Tóm lược

Biết làm thế nào rò rỉ bộ nhớ có thể xảy ra là quan trọng, nhưng chỉ là một phần của toàn bộ hình ảnh. Điều quan trọng nữa là phải nhận ra có vấn đề rò rỉ bộ nhớ trong một ứng dụng hiện có, tìm chúng và khắc phục chúng. Bạn có thể đọc bài viết của tôi Tìm, Sửa và Tránh Rò rỉ Bộ nhớ trong C # .NET: 8 Thực tiễn Tốt nhất để biết thêm thông tin về điều đó.

Hy vọng bạn thích bài viết, và mã hóa hạnh phúc.

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