Cấu tạo của một bộ nhớ Leak


172

Trong phối cảnh .NET:

  • A là gì rò rỉ bộ nhớ ?
  • Làm thế nào bạn có thể xác định xem ứng dụng của bạn bị rò rỉ? Những ảnh hưởng là gì?
  • Làm thế nào bạn có thể ngăn chặn rò rỉ bộ nhớ?
  • Nếu ứng dụng của bạn bị rò rỉ bộ nhớ, nó có biến mất khi quá trình thoát hoặc bị giết không? Hoặc rò rỉ bộ nhớ trong ứng dụng của bạn có ảnh hưởng đến các quy trình khác trên hệ thống ngay cả sau khi hoàn tất quy trình không?
  • Và những gì về mã không được quản lý được truy cập thông qua COM Interop và / hoặc P / Gọi?

Câu trả lời:


110

Giải thích tốt nhất tôi từng thấy là trong Chương 7 của sách điện tử lập trình miễn phí .

Về cơ bản, trong .NET, rò rỉ bộ nhớ xảy ra khi các đối tượng được tham chiếu được root và do đó không thể được thu gom rác. Điều này vô tình xảy ra khi bạn giữ các tham chiếu ngoài phạm vi dự định.

Bạn sẽ biết rằng bạn bị rò rỉ khi bạn bắt đầu nhận OutOfMemoryExceptions hoặc việc sử dụng bộ nhớ của bạn vượt xa những gì bạn mong đợi ( PerfMon có bộ đếm bộ nhớ đẹp).

Hiểu mô hình bộ nhớ của .NET là cách tốt nhất để tránh nó. Cụ thể, hiểu cách thức hoạt động của trình thu gom rác và cách thức tham chiếu hoạt động - một lần nữa, tôi giới thiệu bạn đến chương 7 của cuốn sách điện tử. Ngoài ra, hãy chú ý đến những cạm bẫy phổ biến, có lẽ là sự kiện phổ biến nhất. Nếu đối tượng Một là đăng ký một sự kiện trên đối tượng B , sau đó đối tượng Một sẽ dính vào xung quanh cho đến khi đối tượng B biến mất vì B giữ một tham chiếu đến một . Giải pháp là hủy đăng ký các sự kiện của bạn khi bạn hoàn thành.

Tất nhiên, một hồ sơ bộ nhớ tốt sẽ cho phép bạn xem biểu đồ đối tượng của bạn và khám phá việc lồng / tham chiếu các đối tượng của bạn để xem các tham chiếu đến từ đâu và đối tượng gốc nào chịu trách nhiệm ( hồ sơ kiến ​​cổng đỏ , JetBrains dotMemory, memprofiler thực sự tốt các lựa chọn hoặc bạn có thể sử dụng WinDbgSOS chỉ có văn bản , nhưng tôi thực sự muốn giới thiệu một sản phẩm thương mại / trực quan trừ khi bạn là một bậc thầy thực sự).

Tôi tin rằng mã không được quản lý có thể bị rò rỉ bộ nhớ thông thường, ngoại trừ các tham chiếu được chia sẻ được quản lý bởi trình thu gom rác. Tôi có thể sai về điểm cuối cùng này.


11
Oh bạn thích cuốn sách nào? Thỉnh thoảng tôi thấy tác giả bật lên stackoverflow.
Johnno Nolan

Một số đối tượng .NET cũng có thể tự root và trở nên không thể kiểm soát được. Bất cứ điều gì IDis Dùng nên được xử lý vì điều này.
kyoryu

1
@kyoryu: Làm thế nào để một đối tượng root chính nó?
Andrei Rînea

2
@Andrei: Tôi nghĩ rằng một Thread đang chạy có lẽ là ví dụ tốt nhất về một đối tượng tự root. Một đối tượng đặt tham chiếu đến chính nó ở một vị trí không công khai tĩnh (chẳng hạn như đăng ký một sự kiện tĩnh hoặc triển khai một đơn vị bằng cách khởi tạo trường tĩnh) cũng có thể bắt nguồn từ chính nó, vì không có cách rõ ràng nào để ... ừm ... "nhổ" nó khỏi neo của nó.
Jeffrey Hantin

@Jeffry đây là một cách không thông thường để mô tả những gì đang xảy ra và tôi thích nó!
Lối ra

35

Nói đúng ra, rò rỉ bộ nhớ đang tiêu thụ bộ nhớ "không còn được sử dụng" bởi chương trình.

"Không còn được sử dụng" có nhiều hơn một nghĩa, nó có nghĩa là "không còn tham chiếu đến nó nữa", nghĩa là hoàn toàn không thể phục hồi được, hoặc có thể có nghĩa là, được tham chiếu, có thể phục hồi, không sử dụng nhưng chương trình vẫn giữ các tài liệu tham khảo. Chỉ sau này áp dụng cho .Net cho các đối tượng được quản lý hoàn hảo . Tuy nhiên, không phải tất cả các lớp đều hoàn hảo và tại một thời điểm nào đó, việc triển khai không được quản lý cơ bản có thể làm rò rỉ tài nguyên vĩnh viễn cho quá trình đó.

Trong mọi trường hợp, ứng dụng tiêu tốn nhiều bộ nhớ hơn mức cần thiết. Các tác dụng phụ, tùy thuộc vào lượng đạn bị rò rỉ, có thể đi từ không, đến sự chậm lại do thu thập quá mức, đến một loạt các trường hợp ngoại lệ bộ nhớ và cuối cùng là một lỗi nghiêm trọng sau khi chấm dứt quá trình bắt buộc.

Bạn biết một ứng dụng có vấn đề về bộ nhớ khi giám sát cho thấy ngày càng nhiều bộ nhớ được phân bổ cho quy trình của bạn sau mỗi chu kỳ thu gom rác . Trong trường hợp như vậy, bạn đang giữ quá nhiều bộ nhớ hoặc một số triển khai không được quản lý cơ bản bị rò rỉ.

Đối với hầu hết các rò rỉ, tài nguyên được phục hồi khi quá trình kết thúc, tuy nhiên một số tài nguyên không phải lúc nào cũng được phục hồi trong một số trường hợp chính xác, tay cầm con trỏ GDI nổi tiếng về điều đó. Tất nhiên, nếu bạn có một cơ chế giao tiếp giữa các quá trình, bộ nhớ được phân bổ trong quy trình khác sẽ không được giải phóng cho đến khi quá trình đó giải phóng nó hoặc chấm dứt.


32

Tôi nghĩ rằng câu hỏi "rò rỉ bộ nhớ" và "hiệu ứng là gì" đã được trả lời tốt, nhưng tôi muốn thêm một vài điều nữa vào các câu hỏi khác ...

Làm thế nào để hiểu ứng dụng của bạn bị rò rỉ

Một cách thú vị là mở perfmon và thêm dấu vết cho # byte trong tất cả các bộ sưu tập heap# Gen 2 , trong mỗi trường hợp chỉ nhìn vào quy trình của bạn. Nếu thực hiện một tính năng cụ thể làm cho tổng số byte tăng lên và bộ nhớ đó vẫn được phân bổ sau bộ sưu tập Gen 2 tiếp theo, bạn có thể nói rằng tính năng này làm rò rỉ bộ nhớ.

Làm thế nào để ngăn chặn

Ý kiến ​​tốt khác đã được đưa ra. Tôi chỉ nói thêm rằng có lẽ nguyên nhân thường bị bỏ qua nhất của rò rỉ bộ nhớ .NET là thêm các trình xử lý sự kiện vào các đối tượng mà không xóa chúng. Trình xử lý sự kiện gắn liền với một đối tượng là một dạng tham chiếu đến đối tượng đó, vì vậy sẽ ngăn chặn việc thu thập ngay cả khi tất cả các tham chiếu khác đã biến mất. Luôn nhớ tách các trình xử lý sự kiện (sử dụng -=cú pháp trong C #).

Rò rỉ có biến mất khi quá trình thoát ra không, còn COM thì sao?

Khi tiến trình của bạn thoát, tất cả bộ nhớ được ánh xạ vào không gian địa chỉ của nó sẽ được hệ điều hành lấy lại, bao gồm mọi đối tượng COM được cung cấp từ DLL. Một cách tương đối hiếm khi, các đối tượng COM có thể được phục vụ từ các quy trình riêng biệt. Trong trường hợp này, khi quá trình của bạn thoát, bạn vẫn có thể chịu trách nhiệm về bộ nhớ được phân bổ trong bất kỳ quy trình máy chủ COM nào mà bạn đã sử dụng.


19

Tôi sẽ định nghĩa rò rỉ bộ nhớ là một đối tượng không giải phóng tất cả bộ nhớ được phân bổ sau khi hoàn thành. Tôi đã tìm thấy điều này có thể xảy ra trong ứng dụng của bạn nếu bạn đang sử dụng Windows API và COM (tức là mã không được quản lý có lỗi trong đó hoặc không được quản lý chính xác), trong khung và trong các thành phần của bên thứ ba. Tôi cũng đã tìm thấy không tăng cường sau khi sử dụng một số đối tượng như bút có thể gây ra vấn đề.

Cá nhân tôi đã phải chịu Ngoại lệ bộ nhớ có thể gây ra nhưng không dành riêng cho rò rỉ bộ nhớ trong các ứng dụng mạng chấm. (OOM cũng có thể đến từ ghim xem Ghim Artical ). Nếu bạn không nhận được lỗi OOM hoặc cần xác nhận xem đó có phải là lỗi rò rỉ bộ nhớ hay không thì cách duy nhất là lập hồ sơ cho ứng dụng của bạn.

Tôi cũng sẽ thử và đảm bảo những điều sau:

a) Mọi thứ thực hiện Idis Dùng được xử lý bằng cách sử dụng khối cuối cùng hoặc câu lệnh sử dụng bao gồm bút vẽ, bút, v.v. (một số người tranh luận để đặt mọi thứ thành không có gì ngoài)

b) Bất cứ điều gì có phương thức đóng được đóng lại bằng cách sử dụng cuối cùng hoặc câu lệnh sử dụng (mặc dù tôi đã tìm thấy việc sử dụng không phải lúc nào cũng đóng tùy thuộc vào việc bạn đã khai báo đối tượng bên ngoài câu lệnh sử dụng)

c) Nếu bạn đang sử dụng API mã / windows không được quản lý mà các API này được xử lý chính xác sau đó. (một số có phương pháp dọn dẹp để giải phóng tài nguyên)

Hi vọng điêu nay co ich.


19

Nếu bạn cần chẩn đoán rò rỉ bộ nhớ trong .NET, hãy kiểm tra các liên kết sau:

http://msdn.microsoft.com/en-us/magazine/cc163833.aspx

http://msdn.microsoft.com/en-us/magazine/cc164138.aspx

Những bài viết này mô tả cách tạo kết xuất bộ nhớ cho quá trình của bạn và cách phân tích nó để trước tiên bạn có thể xác định xem rò rỉ của bạn không được quản lý hoặc quản lý và nếu nó được quản lý, làm thế nào để biết nó đến từ đâu.

Microsoft cũng có một công cụ mới hơn để hỗ trợ tạo các bãi đổ vỡ, để thay thế ADPlus, được gọi là DebugDiag.

http://www.microsoft.com/doads/details.aspx?FamilyID=28bd5941-c458-46f1-b24d-f60151d875a3&displaylang=en



15

Giải thích tốt nhất về cách thức hoạt động của trình thu gom rác là trong Jeff Richters CLR thông qua sách C # , (Ch. 20). Đọc điều này cung cấp một nền tảng tuyệt vời để hiểu làm thế nào các đối tượng tồn tại.

Một trong những nguyên nhân phổ biến nhất của các đối tượng root vô tình là do kết nối các sự kiện bên ngoài một lớp. Nếu bạn kết nối một sự kiện bên ngoài

ví dụ

SomeExternalClass.Changed += new EventHandler(HandleIt);

và quên mở khóa cho nó khi bạn loại bỏ, sau đó someExternalClass có một tham chiếu đến lớp của bạn.

Như đã đề cập ở trên, trình lược tả bộ nhớ SciTech rất tuyệt vời trong việc hiển thị cho bạn nguồn gốc của các đối tượng mà bạn nghi ngờ đang bị rò rỉ.

Nhưng cũng có một cách rất nhanh để kiểm tra một loại cụ thể là chỉ sử dụng WnDBG (thậm chí bạn có thể sử dụng điều này trong cửa sổ ngay lập tức VS.NET trong khi đính kèm):

.loadby sos mscorwks
!dumpheap -stat -type <TypeName>

Bây giờ làm một cái gì đó mà bạn nghĩ sẽ loại bỏ các đối tượng của loại đó (ví dụ: đóng một cửa sổ). Thật tiện dụng ở đây để có một nút gỡ lỗi ở đâu đó sẽ chạy System.GC.Collect()một vài lần.

Sau đó chạy !dumpheap -stat -type <TypeName>lại. Nếu con số không giảm hoặc không giảm nhiều như bạn mong đợi, thì bạn có cơ sở để điều tra thêm. (Tôi đã nhận được lời khuyên này từ một cuộc hội thảo được đưa ra bởi Ingo Rammer ).


14

Tôi đoán trong một môi trường được quản lý, một rò rỉ sẽ là bạn giữ một tài liệu tham khảo không cần thiết cho một khối lớn bộ nhớ xung quanh.


11

Tại sao mọi người nghĩ rằng rò rỉ bộ nhớ trong .NET không giống với bất kỳ rò rỉ nào khác?

Rò rỉ bộ nhớ là khi bạn gắn vào tài nguyên và không để nó đi. Bạn có thể làm điều này cả trong mã hóa được quản lý và không được quản lý.

Về .NET và các công cụ lập trình khác, đã có ý tưởng về thu gom rác và các cách khác để giảm thiểu các tình huống sẽ khiến ứng dụng của bạn bị rò rỉ. Nhưng phương pháp tốt nhất để ngăn chặn rò rỉ bộ nhớ là bạn cần hiểu mô hình bộ nhớ cơ bản của mình và cách mọi thứ hoạt động, trên nền tảng bạn đang sử dụng.

Tin rằng GC và phép thuật khác sẽ dọn sạch mớ hỗn độn của bạn là con đường ngắn để rò rỉ bộ nhớ, và sẽ khó tìm thấy sau này.

Khi mã hóa không được quản lý, bạn thường đảm bảo dọn dẹp, bạn biết rằng các tài nguyên bạn nắm giữ, sẽ là trách nhiệm của bạn để dọn sạch, không phải của người gác cổng.

Mặt khác, trong .NET, rất nhiều người nghĩ rằng GC sẽ dọn sạch mọi thứ. Vâng, nó làm một số cho bạn, nhưng bạn cần chắc chắn rằng nó là như vậy. .NET có rất nhiều thứ, vì vậy bạn không phải lúc nào cũng biết mình đang xử lý tài nguyên được quản lý hay không được quản lý và bạn cần đảm bảo những gì bạn đang xử lý. Xử lý phông chữ, tài nguyên GDI, thư mục hoạt động, cơ sở dữ liệu, vv thường là những thứ bạn cần chú ý.

Theo thuật ngữ được quản lý, tôi sẽ đặt cổ lên dây chuyền để nói rằng nó sẽ biến mất một khi quá trình bị giết / loại bỏ.

Tôi thấy rất nhiều người có điều này mặc dù, và tôi thực sự hy vọng điều này sẽ kết thúc. Bạn không thể yêu cầu người dùng chấm dứt ứng dụng của bạn để dọn dẹp mớ hỗn độn của bạn! Hãy xem một trình duyệt, có thể là IE, FF, v.v., sau đó mở, giả sử, Google Reader, để nó ở lại trong một vài ngày và xem xét những gì sẽ xảy ra.

Nếu sau đó bạn mở một tab khác trong trình duyệt, lướt đến một trang web nào đó, sau đó đóng tab lưu trữ trang khác khiến trình duyệt bị rò rỉ, bạn có nghĩ trình duyệt sẽ giải phóng bộ nhớ không? Không như vậy với IE. Trên máy tính của tôi, IE sẽ dễ dàng ăn 1 GiB bộ nhớ trong một khoảng thời gian ngắn (khoảng 3-4 ngày) nếu tôi sử dụng Google Reader. Một số tờ báo thậm chí còn tồi tệ hơn.


10

Tôi đoán trong một môi trường được quản lý, một rò rỉ sẽ là bạn giữ một tài liệu tham khảo không cần thiết cho một khối lớn bộ nhớ xung quanh.

Chắc chắn rồi. Ngoài ra, không sử dụng phương thức .Dispose () trên các đối tượng dùng một lần khi thích hợp có thể gây rò rỉ mem. Cách dễ nhất để làm điều đó là với một khối sử dụng vì nó tự động thực thi .Dispose () ở cuối:

StreamReader sr;
using(sr = new StreamReader("somefile.txt"))
{
    //do some stuff
}

Và nếu bạn tạo một lớp đang sử dụng các đối tượng không được quản lý, nếu bạn không triển khai IDis Dùng chính xác, bạn có thể gây rò rỉ bộ nhớ cho người dùng của lớp.


9

Tất cả các rò rỉ bộ nhớ được giải quyết bằng cách chấm dứt chương trình.

Bộ nhớ bị rò rỉ đủ và Hệ điều hành có thể quyết định giải quyết vấn đề thay cho bạn.


8

Tôi sẽ đồng ý với Bernard như trong .net về việc rò rỉ mem sẽ là gì.

Bạn có thể cấu hình ứng dụng của mình để xem bộ nhớ sử dụng và xác định rằng nếu ứng dụng đó quản lý nhiều bộ nhớ khi không nên sử dụng thì bạn có thể nói nó bị rò rỉ.

Theo thuật ngữ được quản lý, tôi sẽ đặt cổ lên dây chuyền để nói rằng nó sẽ biến mất một khi quá trình bị giết / loại bỏ.

Mã không được quản lý là con thú riêng của nó và nếu có rò rỉ tồn tại bên trong nó, nó sẽ tuân theo một mem chuẩn. định nghĩa rò rỉ.


7

Cũng nên nhớ rằng .NET có hai heap, một là heap đối tượng lớn. Tôi tin rằng các đối tượng khoảng 85k hoặc lớn hơn được đưa vào đống này. Heap này có một quy tắc trọn đời khác với heap thông thường.

Nếu bạn đang tạo các cấu trúc bộ nhớ lớn (Từ điển hoặc Danh sách), bạn nên thận trọng tìm kiếm các quy tắc chính xác là gì.

Theo như lấy lại bộ nhớ khi chấm dứt quá trình, trừ khi bạn chạy Win98 hoặc tương đương, mọi thứ sẽ được giải phóng trở lại HĐH khi chấm dứt. Các ngoại lệ duy nhất là những thứ được mở quy trình chéo và một quy trình khác vẫn có tài nguyên mở.

Đối tượng COM có thể khó khăn tho. Nếu bạn luôn sử dụng IDisposemô hình, bạn sẽ an toàn. Nhưng tôi đã chạy qua một vài hội đồng interop thực hiện IDispose. Chìa khóa ở đây là gọi Marshal.ReleaseCOMObjectkhi bạn hoàn thành nó. Các đối tượng COM vẫn sử dụng tính năng tham chiếu COM tiêu chuẩn.


6

Tôi tìm thấy .Net Memory Profiler một trợ giúp rất tốt khi tìm thấy rò rỉ bộ nhớ trong .Net. Nó không miễn phí như Microsoft CLR Profiler, nhưng theo quan điểm của tôi thì nhanh hơn và nhiều hơn. Một


1

Một định nghĩa là: Không thể giải phóng bộ nhớ không thể truy cập, không thể phân bổ cho quy trình mới trong quá trình thực hiện quy trình phân bổ. Nó chủ yếu có thể được chữa khỏi bằng cách sử dụng các kỹ thuật GC hoặc được phát hiện bởi các công cụ tự động.

Để biết thêm thông tin, vui lòng truy cập http://all-about-java-and-weblogic-server.blogspot.in/2014/01/what-is-memory-leak-in-java.html .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.