2012-01-11 18 views
11

Tôi gặp phải một số sự cố với WeakHashMap.WeakHashMap có bị xóa trong toàn bộ GC không?

xem xét mẫu mã này:

List<byte[]> list = new ArrayList<byte[]>(); 

Map<String, Calendar> map = new WeakHashMap<String, Calendar>(); 
String anObject = new String("string 1"); 
String anOtherObject = new String("string 2"); 

map.put(anObject, Calendar.getInstance()); 
map.put(anOtherObject, Calendar.getInstance()); 
// In order to test if the weakHashMap works, i remove the StrongReference in this object 
anObject = null; 
int i = 0; 
while (map.size() == 2) { 
    byte[] tab = new byte[10000]; 
    System.out.println("iteration " + i++ + "map size :" + map.size()); 
    list.add(tab); 
} 
System.out.println("Map size " + map.size()); 

Mã này hoạt động. Bên trong vòng lặp, tôi đang tạo đối tượng.Khi một GC nhỏ xảy ra, kích thước bản đồ bằng 1 ở lần lặp 1360. Tất cả đều ổn.

Bây giờ khi tôi bình luận dòng này:

//anObject = null; 

Tôi hy vọng sẽ có một OutOfMemoryError vì mapSize luôn bằng 2. Tuy nhiên tại phiên thứ 26XXX, một GC đầy đủ xảy ra và kích thước bản đồ bằng 0. Tôi không hiểu tại sao?

Tôi nghĩ rằng bản đồ không nên bị xóa vì cũng có các tham chiếu mạnh mẽ cho cả hai đối tượng.

+0

Tôi nghĩ thử nghiệm của bạn không chính xác. Nếu bạn thay đổi 'while (map.size() == 2) {' thành 'while (map.size()> 0) {', hai phép thử sẽ kết thúc cho đến khi bản đồ trống, bất kể bạn nhận xét 'anObject = null' hay không. BTW, tôi đã thử nó rồi. – donnior

+0

In 'anObject' và' anOtherObject' ở cuối. Trình biên dịch thấy rằng bạn không còn sử dụng chúng nữa và có thể xóa chúng trước đó. –

Trả lời

10

The-chỉ trong thời gian biên dịch phân tích mã, thấy rằng anObjectanOtherObject không được sử dụng sau khi vòng lặp, và loại bỏ chúng khỏi bảng biến địa phương hoặc đặt họ null, trong khi vòng lặp vẫn chạy. Điều này được gọi là biên dịch OSR.

Sau đó, GC thu thập các chuỗi vì không có tham chiếu mạnh mẽ đến chúng.

Nếu bạn đã sử dụng anObject sau vòng lặp, bạn vẫn sẽ nhận được OutOfMemoryError.

Cập nhật: Bạn sẽ tìm thấy một cuộc thảo luận chi tiết hơn về OSR compilation trong blog của tôi.

+0

Tôi nghĩ rằng bạn đang chính xác đúng - nhưng đây không phải là một khả năng tối ưu hóa JIT có khả năng phá vỡ? Nếu 'anObject' có một finalizer và nó là GC'd trước khi tham chiếu đó biến mất finalizer sẽ thực thi trước khi nó có ý nghĩa. – berry120

+0

Điều gì có thể phá vỡ? Khi finalizer được chạy tham chiếu mạnh mẽ không còn tồn tại. – Joni

+0

Nó có thể phá vỡ trong ý nghĩa nó có thể chạy một finalizer sớm hơn bạn mong đợi nó; trước khi tham chiếu cứng đã thực sự vượt quá phạm vi. – berry120

7

Bit của đào tiết lộ rằng đây là một cách rõ ràng bao gồm trong JLS, phần 12.6.1:

biến đổi Tối ưu hóa của một chương trình có thể được thiết kế làm giảm số lượng các đối tượng mà có thể truy cập được ít hơn so với những mà ngây thơ được coi là có thể truy cập. Ví dụ: trình biên dịch hoặc trình tạo mã có thể chọn đặt biến hoặc tham số sẽ không còn được sử dụng để vô hiệu để lưu trữ cho đối tượng như vậy có khả năng có thể phục hồi sớm hơn.

(bolding là bổ sung của tôi.)

http://java.sun.com/docs/books/jls/third_edition/html/execution.html#12.6.1

Vì vậy, trong bản chất, JIT được phép loại bỏ tài liệu tham khảo mạnh mẽ bất cứ khi nào nó muốn nếu nó có thể làm việc ra rằng họ sẽ không bao giờ được sử dụng một lần nữa - đó là chính xác những gì đang xảy ra ở đây. Tuy nhiên, đây là một câu hỏi tuyệt vời và làm cho một puzzler tuyệt vời có thể dễ dàng hiển thị chỉ vì một đối tượng dường như có một tham chiếu mạnh mẽ trong phạm vi, không nhất thiết có nghĩa là nó chưa được thu thập rác. Theo sau từ này nó có nghĩa là bạn rõ ràng không thể đảm bảo bất cứ điều gì về khi một finalizer sẽ chạy, điều này thậm chí có thể trong trường hợp nó có vẻ như đối tượng vẫn còn trong phạm vi!

Ví dụ:

List<byte[]> list = new ArrayList<byte[]>(); 

Object thing = new Object() { 
    protected void finalize() { 
     System.out.println("here"); 
    } 
}; 
WeakReference<Object> ref = new WeakReference<Object>(thing); 

while(ref.get()!=null) { 
    list.add(new byte[10000]); 
} 
System.out.println("bam"); 

Trên đây là một ví dụ đơn giản cho thấy các đối tượng được hoàn thành và GC'd đầu tiên mặc dù tài liệu tham khảo để thing vẫn còn tồn tại

7
(ở đây được in, sau đó bam.)

Chỉ cần thêm một điều nhỏ vào câu trả lời tuyệt vời từ Joni Salonenberry120. Có thể thấy rằng JIT thực sự chịu trách nhiệm về việc "loại bỏ biến" chỉ đơn giản là tắt nó đi với -Djava.compiler=NONE. Khi bạn tắt nó đi, bạn sẽ có được OOME.

Nếu chúng tôi muốn biết điều gì đang xảy ra dưới mui xe, tùy chọn XX:+PrintCompilation hiển thị hoạt động JIT. Sử dụng nó với mã từ câu hỏi đầu ra chúng tôi nhận được là như sau:

1  java.lang.String::hashCode (64 bytes) 
2  java.lang.String::charAt (33 bytes) 
3  java.lang.String::indexOf (151 bytes) 
4  java.util.ArrayList::add (29 bytes) 
5  java.util.ArrayList::ensureCapacity (58 bytes) 
6 ! java.lang.ref.ReferenceQueue::poll (28 bytes) 
7  java.util.WeakHashMap::expungeStaleEntries (125 bytes) 
8  java.util.WeakHashMap::size (18 bytes) 
1%  WeakHM::main @ 63 (126 bytes) 
Map size 0 

Việc lập cuối cùng (với @ cờ) là một OSR (Trên stack thay thế) biên soạn (kiểm tra https://gist.github.com/1165804#file_notes.md để biết thêm chi tiết). Nói một cách đơn giản, nó cho phép VM thay thế một phương thức trong khi nó đang chạy và nó được sử dụng để cải thiện hiệu suất của các phương thức Java bị mắc kẹt trong các vòng lặp. Tôi đoán rằng sau khi biên dịch này được kích hoạt, JIT sẽ loại bỏ các biến không còn được sử dụng nữa.

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