2009-03-09 32 views
36

Tôi đang thực hiện một số ứng dụng từ nhà phát triển trước đó. Khi tôi chạy các ứng dụng thông qua Eclipse, tôi thấy việc sử dụng bộ nhớ và kích thước heap tăng lên rất nhiều. Sau khi điều tra thêm, tôi thấy rằng họ đang tạo ra một vật thể liên tục trong một vòng lặp cũng như những thứ khác.Một số phương pháp hay nhất về quản lý bộ nhớ Java là gì?

Tôi bắt đầu trải qua và làm một số việc dọn dẹp. Nhưng tôi càng trải qua nhiều câu hỏi, tôi càng thích "điều này có thực sự làm gì không?"

Ví dụ: thay vì khai báo biến ngoài vòng lặp được đề cập ở trên và chỉ đặt giá trị của nó trong vòng lặp ... chúng tạo đối tượng trong vòng lặp. Những gì tôi có nghĩa là:

for(int i=0; i < arrayOfStuff.size(); i++) { 
    String something = (String) arrayOfStuff.get(i); 
    ... 
} 

so

String something = null; 
for(int i=0; i < arrayOfStuff.size(); i++) { 
    something = (String) arrayOfStuff.get(i); 
} 

Am tôi không chính xác khi nói rằng vòng lặp chốt là tốt hơn? Có lẽ tôi đã sai.

Ngoài ra, còn sau vòng lặp thứ hai ở trên, tôi đặt "cái gì đó" trở lại thành vô giá trị? Điều đó sẽ xóa một số bộ nhớ?

Trong cả hai trường hợp, một số phương pháp hay nhất về quản lý bộ nhớ tốt mà tôi có thể thực hiện theo đó sẽ giúp giữ mức sử dụng bộ nhớ của tôi thấp trong các ứng dụng của tôi là gì?

Cập nhật:

tôi đánh giá cao phản hồi của mọi người cho đến nay. Tuy nhiên, tôi đã không thực sự hỏi về các vòng trên (mặc dù theo lời khuyên của bạn tôi đã quay trở lại vòng đầu tiên). Tôi đang cố gắng thực hiện một số phương pháp hay nhất mà tôi có thể theo dõi. Một cái gì đó trên dòng của "khi bạn đang thực hiện bằng cách sử dụng một bộ sưu tập, xóa nó ra". Tôi chỉ thực sự cần phải chắc chắn rằng không có nhiều bộ nhớ đang được đưa lên bởi các ứng dụng này.

+0

Chỉnh sửa của bạn: Như ba người đã nói cho đến nay, hãy lập hồ sơ! –

+0

Tôi đang tìm một số thực hành cụ thể để sử dụng vì vậy tôi không cần phải có ai đó cấu hình nó. Tôi thà phát triển nó đúng cách ngay từ đầu hơn là viết mã, hy vọng nó hoạt động, lập hồ sơ và sửa lỗi. – Ascalonian

+2

Các "thực hành cụ thể" là viết mã có cấu trúc tốt mà không thực hiện tối ưu hóa sớm, và sau đó lược tả nó để tìm ra những gì cần tối ưu hóa. –

Trả lời

36

Đừng cố gắng outsmart VM. Vòng lặp đầu tiên là phương pháp hay nhất được đề xuất, cả về hiệu suất và khả năng bảo trì. Thiết lập tham chiếu trở lại null sau khi vòng lặp sẽ không đảm bảo phát hành bộ nhớ ngay lập tức. GC sẽ làm tốt nhất công việc của mình khi bạn sử dụng phạm vi tối thiểu có thể.

Sách bao gồm những chi tiết này một cách chi tiết (từ góc nhìn của người dùng) là Effective Java 2Implementation Patterns.

Nếu bạn quan tâm để tìm hiểu thêm về hiệu suất và người dùng máy ảo, bạn cần xem các cuộc đàm phán hoặc đọc sách từ Brian Goetz.

+0

" sử dụng phạm vi tối thiểu có thể "được coi là thực hành tốt nhất nói chung? – KillBill

+0

Tôi nghĩ rằng nói chung là có. Nhưng tất nhiên phải có ngoại lệ cho sự khái quát hóa đó. – cherouvim

9

Hai vòng lặp đó tương đương ngoại trừ phạm vi something; xem this question để biết chi tiết.

Thực tiễn tốt nhất chung? Umm, hãy xem: không lưu trữ lượng lớn dữ liệu trong các biến tĩnh trừ khi bạn có lý do chính đáng. Loại bỏ các đối tượng lớn khỏi bộ sưu tập khi bạn đã hoàn thành chúng. Và vâng, "Đo, đừng đoán." Sử dụng một profiler để xem nơi bộ nhớ đang được cấp phát.

+3

+1 chỉ dành cho "Đo lường, đừng đoán." –

5

Hai vòng sẽ sử dụng về cơ bản cùng một lượng bộ nhớ, mọi khác biệt sẽ không đáng kể. "String cái gì đó" chỉ tạo ra một tham chiếu đến một đối tượng, không phải là một đối tượng mới trong chính nó và do đó bất kỳ bộ nhớ bổ sung được sử dụng là nhỏ. Thêm vào đó, trình biên dịch/kết hợp với JVM có khả năng sẽ tối ưu hóa mã được tạo ra.

Để thực hành quản lý bộ nhớ, bạn nên thực sự cố gắng để cấu hình bộ nhớ của bạn tốt hơn để hiểu nơi mà các nút cổ chai thực sự là.Nhìn đặc biệt là các tham chiếu tĩnh trỏ đến một phần lớn bộ nhớ, vì nó sẽ không bao giờ được thu thập.

Bạn cũng có thể xem Tham khảo yếu và các lớp quản lý bộ nhớ chuyên dụng khác.

Cuối cùng, hãy nhớ rằng nếu một ứng dụng chiếm bộ nhớ, có thể có một lý do cho nó ....

Cập nhật Chìa khóa để quản lý bộ nhớ là cấu trúc dữ liệu, cũng như bao nhiêu hiệu suất bạn cần/khi nào. Sự cân bằng thường là giữa chu kỳ bộ nhớ và CPU.

Ví dụ: rất nhiều bộ nhớ có thể bị chiếm bởi bộ nhớ đệm, đặc biệt ở đó để cải thiện hiệu suất vì bạn đang cố gắng tránh hoạt động tốn kém.

Vì vậy, hãy suy nghĩ thông qua các cấu trúc dữ liệu của bạn và đảm bảo rằng bạn không giữ mọi thứ trong bộ nhớ lâu hơn bạn phải làm. Nếu đó là ứng dụng web, tránh lưu trữ nhiều dữ liệu vào biến phiên, tránh có tham chiếu tĩnh đến các nhóm bộ nhớ khổng lồ, v.v.

4

Vòng lặp đầu tiên tốt hơn. Bởi vì

  • biến một cái gì đó sẽ được rõ ràng nhanh hơn (lý thuyết)
  • chương trình là tốt hơn để đọc.

Nhưng từ điểm bộ nhớ này không liên quan.

Nếu bạn có vấn đề về bộ nhớ thì bạn nên cấu hình nơi nó được tiêu thụ.

+0

Xin lỗi, hãy sửa tôi nếu tôi sai, nhưng vòng lặp đầu tiên cấp phát bộ nhớ cho một biến mới trên mỗi vòng lặp. Nếu vòng lặp chạy 100 lần, làm thế nào đến nó tốt hơn tạo 100 chuỗi thay vì chỉ ghi đè cùng một và phân bổ bộ nhớ cho chỉ 1 chuỗi (như trong vòng lặp thứ hai)? – Mapisto

+0

Bộ nhớ trên ngăn xếp cho tất cả các biến cục bộ của phương thức được phân bổ trên mục nhập phương thức chứ không phải trên khai báo điểm trong mã. Kích thước của biến đó là 4 byte. (có thể 8 byte trên máy ảo 64 bit). Kích thước cho String được cấp phát trên heap chứ không phải trên stack. Phân bổ này xảy ra trên từ khóa "mới". Không có sự khác biệt trong việc phân bổ các chuỗi. – Horcrux7

8

Không có đối tượng nào được tạo trong cả hai mẫu mã của bạn. Bạn chỉ cần đặt một tham chiếu đối tượng cho một chuỗi đã có trong arrayOfStuff. Vì vậy, không có sự khác biệt.

1

Vâng, vòng lặp đầu tiên thực sự tốt hơn, vì phạm vi của thứ gì đó nhỏ hơn. Về quản lý bộ nhớ - nó không tạo ra sự khác biệt lớn.

Hầu hết các vấn đề về bộ nhớ Java đều xuất hiện khi bạn lưu trữ các đối tượng trong bộ sưu tập, nhưng quên xóa chúng đi. Nếu không, GC làm cho công việc của mình khá tốt.

1

Ví dụ đầu tiên là tốt. Không có bất kỳ phân bổ bộ nhớ nào đang diễn ra ở đó, ngoài việc phân bổ biến stack và deallocation mỗi lần thông qua vòng lặp (rất rẻ và nhanh).

Lý do là tất cả những gì đang được 'cấp phát' là tham chiếu, là biến ngăn xếp 4 byte (trên hầu hết các hệ thống 32 bit). Một biến ngăn xếp là 'được phân bổ' bằng cách thêm vào một địa chỉ bộ nhớ đại diện cho đỉnh của ngăn xếp, và vì vậy rất nhanh chóng và rẻ tiền.

Những gì bạn cần phải cẩn thận về là cho vòng như:

for (int i = 0; i < some_large_num; i++) 
{ 
    String something = new String(); 
    //do stuff with something 
} 

như được thực sự làm allocatiton nhớ.

+0

Thậm chí không có phân bổ trên ngăn xếp cho mỗi lần lặp lại. Chúng được phân bổ trong bảng biến cục bộ của phương thức (một lần) nhưng biến sẽ tự động đi ngoài phạm vi khi vòng lặp kết thúc. –

+0

vâng, nhưng một đối tượng String mới được tạo ra mỗi lần lặp lại là tốn kém hơn so với phân bổ stack. – workmad3

5

JVM là cách tốt nhất để giải phóng các đối tượng sống ngắn. Cố gắng không phân bổ các đối tượng bạn không cần. Nhưng bạn không thể tối ưu hóa việc sử dụng bộ nhớ cho đến khi bạn hiểu khối lượng công việc của mình, tuổi thọ của đối tượng và kích thước đối tượng. Một hồ sơ có thể cho bạn biết điều này.

Cuối cùng, điều số 1 bạn phải tránh làm: không bao giờ sử dụng Trình tổng hợp.Các finalizers can thiệp vào việc thu gom rác, vì đối tượng không thể được giải phóng nhưng phải được xếp hàng đợi để hoàn thành, có thể có hoặc không xảy ra. Tốt nhất là không bao giờ sử dụng finalizers.

Đối với việc sử dụng bộ nhớ bạn đang thấy trong Eclipse, nó không nhất thiết phải có liên quan. GC sẽ thực hiện công việc của mình dựa trên dung lượng bộ nhớ trống. Nếu bạn có nhiều bộ nhớ trống, bạn có thể không thấy một GC nào trước khi ứng dụng bị tắt. Nếu bạn thấy ứng dụng của mình hết bộ nhớ thì chỉ có một trình thu thập thông tin thực sự mới có thể cho bạn biết nơi rò rỉ hoặc thiếu hiệu quả.

+0

Trong ngôn ngữ hướng đối tượng, rác được thu thập, hiệu suất kém thường được gây ra bởi số lượng lớn các đối tượng tồn tại lâu dài, đặc biệt là nếu có nhiều lớp bố cục. Ví dụ, nếu tôi tạo một đối tượng bảng tính, với một mảng 2D của các đối tượng ô, trong đó mỗi đối tượng chứa một đối tượng màu, đối tượng giá trị và đối tượng định dạng, và mỗi đối tượng định dạng chứa ..... Vâng, bạn có ý tưởng. Nó sẽ mất GC một thời gian dài để đối phó với đồ thị đối tượng. Ngoài ra các lớp của bộ nhớ indirection gây lag (nó không trong Smalltalk, anyway. Tôi không chắc chắn về Java). – ahoffer

1

Nếu bạn chưa có, tôi khuyên bạn nên cài đặt Eclipse Test & Performance Tools Platform (TPTP). Nếu bạn muốn đổ và kiểm tra đống, hãy kiểm tra các công cụ SDK jmap and jhat. Đồng thời xem Monitoring and Managing Java SE 6 Platform Applications.

+1

Vì TPTP có vẻ tốt và thực sự đã chết vào lúc này (5 năm sau), hãy dùng jvisualvm đi kèm với JDK - và phân tích nhật ký gc (http://www.tagtraum.com/gcviewer.html hoặc https://github.com/chewiebug/GCViewer). –

4

Theo tôi, bạn nên tránh tối ưu hóa vi mô như thế này. Chúng tốn rất nhiều chu kỳ não, nhưng phần lớn thời gian có ít tác động.

Ứng dụng của bạn có thể có một vài cấu trúc dữ liệu trung tâm. Đó là những thứ bạn nên lo lắng. Ví dụ, nếu bạn điền chúng preallocate chúng với một ước tính tốt về kích thước, để tránh lặp lại thay đổi kích thước của cấu trúc bên dưới. Điều này đặc biệt áp dụng cho StringBuffer, ArrayList, HashMap và các loại tương tự. Thiết kế quyền truy cập của bạn vào những cấu trúc đó tốt, vì vậy bạn không phải sao chép nhiều.

Sử dụng các thuật toán thích hợp để truy cập cấu trúc dữ liệu. Ở cấp độ thấp nhất, giống như vòng lặp bạn đã đề cập, sử dụng Iterator s hoặc ít nhất là tránh gọi số .size() mọi lúc. (Có, bạn đang yêu cầu danh sách mỗi lần cho kích thước của nó, mà hầu hết thời gian không thay đổi.) BTW, tôi thường thấy một lỗi tương tự với Map s. Mọi người lặp lại trên keySet()get mỗi giá trị, thay vì chỉ lặp lại trên entrySet() ngay từ đầu. Trình quản lý bộ nhớ sẽ cảm ơn bạn về các chu kỳ CPU phụ.

2

Như một áp phích ở trên được đề xuất, hãy sử dụng profiler để đo mức sử dụng bộ nhớ (và/hoặc cpu) của một số phần nhất định trong chương trình của bạn thay vì cố đoán nó. Bạn có thể ngạc nhiên trước những gì bạn tìm được!

Có một lợi ích bổ sung cho điều đó. Bạn sẽ hiểu về ngôn ngữ lập trình và ứng dụng của mình nhiều hơn.

Tôi sử dụng VisualVM để lược tả và giới thiệu nó rất nhiều. Nó đi kèm với phân phối jdk/jre.

0

"Tôi không chính xác để nói rằng vòng dưới tốt hơn?", Câu trả lời là KHÔNG, không chỉ tốt hơn, trong trường hợp tương tự là cần thiết ... Định nghĩa biến (không phải là nội dung), được thực hiện trong bộ nhớ heap, và bị giới hạn, trong ví dụ đầu tiên, mỗi vòng lặp tạo một cá thể trong bộ nhớ này và nếu kích thước của "arrayOfStuff" là lớn, có thể xảy ra "Lỗi bộ nhớ ngoài: không gian java heap" ....

0

Từ những gì tôi hiểu bạn đang xem xét, vòng lặp dưới cùng là không tốt hơn. Lý do là ngay cả khi bạn đang cố gắng sử dụng lại một tham chiếu (Ex-something), thực tế là đối tượng (Ex - arrayOfStuff.get (i)) vẫn đang được tham chiếu từ List (arrayOfStuff). Để làm cho các đối tượng đủ điều kiện để thu thập, chúng không nên được tham chiếu từ bất kỳ nơi nào. Nếu bạn chắc chắn về cuộc sống của Danh sách sau thời điểm này, bạn có thể quyết định xóa/giải phóng các đối tượng khỏi Danh sách, trong vòng lặp riêng biệt.

Tối ưu hóa có thể được thực hiện từ góc nhìn tĩnh (tức là không có sửa đổi nào xảy ra với Danh sách này từ bất kỳ chủ đề nào khác), tốt hơn là tránh lặp lại kích thước(). Đó là nếu bạn không mong đợi kích thước thay đổi, thì tại sao lại tính toán nó một lần nữa và một lần nữa; sau khi tất cả nó không phải là một mảng.length, nó là list.size().

Các vấn đề liên quan