Khi nào bạn sẽ sử dụng WeakHashMap hoặc WeakReference?


163

Việc sử dụng các tài liệu tham khảo yếu là điều mà tôi chưa bao giờ thấy việc triển khai vì vậy tôi đang cố gắng tìm hiểu trường hợp sử dụng cho chúng là gì và cách triển khai sẽ hoạt động. Khi nào bạn cần sử dụng một WeakHashMaphoặc WeakReferencenó được sử dụng như thế nào?



Câu trả lời:


96

Một vấn đề với các tài liệu tham khảo mạnh là bộ nhớ đệm, đặc biệt với các cấu trúc rất lớn như hình ảnh. Giả sử bạn có một ứng dụng phải hoạt động với hình ảnh do người dùng cung cấp, như công cụ thiết kế trang web tôi làm việc. Đương nhiên, bạn muốn lưu trữ những hình ảnh này, vì tải chúng từ đĩa rất tốn kém và bạn muốn tránh khả năng có hai bản sao của hình ảnh (có khả năng khổng lồ) trong bộ nhớ cùng một lúc.

Vì bộ đệm hình ảnh được cho là ngăn chúng tôi tải lại hình ảnh khi chúng tôi không thực sự cần, bạn sẽ nhanh chóng nhận ra rằng bộ đệm phải luôn chứa tham chiếu đến bất kỳ hình ảnh nào đã có trong bộ nhớ. Tuy nhiên, với các tham chiếu mạnh thông thường, chính tham chiếu đó sẽ buộc hình ảnh lưu lại trong bộ nhớ, điều này đòi hỏi bạn phải xác định bằng cách nào đó khi hình ảnh không còn cần thiết trong bộ nhớ và xóa nó khỏi bộ đệm, để nó đủ điều kiện để thu gom rác. Bạn buộc phải sao chép hành vi của trình thu gom rác và xác định thủ công xem một đối tượng có nên có trong bộ nhớ hay không.

Hiểu các tài liệu tham khảo yếu , Ethan Nicholas


43
Sẽ không SoftReferences tốt hơn trong trường hợp này, tức là các tài liệu tham khảo chỉ được thu thập khi bộ nhớ bắt đầu hết.
JesperE

Tôi hơi bối rối ... giả sử rằng tôi có bộ đệm hình ảnh SWT. Hình ảnh SWT cần được hiển thị thông qua phương thức dispose () để giải phóng Tài nguyên SO. Nếu tôi sử dụng WeakHashMap để lưu trữ chúng, hãy hiển thị chính xác GC sẽ loại bỏ đối tượng?
marcolopes

2
@marcolopes, GC sẽ sử dụng bộ hoàn thiện trên bất kỳ đối tượng nào khác. Có vẻ như SWT không thích nó khi bạn làm điều đó, vì vậy tôi không nghĩ bạn có thể quản lý tài nguyên hệ điều hành với a WeakHashMap.
Jacob Krall

@marcolopes, (Tôi sẽ giả sử rằng GC của bạn đảm bảo một cuộc gọi để hoàn tất trước khi nó lấy lại bộ nhớ.) Nếu việc xử lý được thực hiện trong bộ hoàn thiện bộ đệm, tất cả đều ổn. Nếu vứt bỏ là thứ bạn phải gọi thủ công, thì 1) mở rộng lớp và đặt vứt vào bộ hoàn thiện, hoặc 2) sử dụng ảo ảnh để theo dõi và chạy xử lý tương ứng. Tùy chọn 2 tốt hơn (tránh các lỗi phục hồi và cung cấp cho bạn khả năng chạy xử lý trên luồng khác), nhưng tùy chọn 1 dễ thực hiện hơn nếu không có lớp trợ giúp.
Pacerier


55

WeakReference đấu với SoftReference

Một điểm khác biệt cần làm rõ là sự khác biệt giữa a WeakReferencevà a SoftReference.

Về cơ bản một WeakReferencesẽ là GC-d bởi JVM háo hức, một khi đối tượng tham chiếu không có người chăm chỉ tham chiếu đến nó. Mặt khác, một SoftReferenceđối tượng d sẽ có xu hướng bị người thu gom rác bỏ lại cho đến khi nó thực sự cần lấy lại bộ nhớ.

Bộ đệm trong đó các giá trị được giữ bên trong WeakReferences sẽ khá vô dụng (trong WeakHashMapđó, đó là các khóa được tham chiếu yếu). SoftReferencesrất hữu ích để bọc các giá trị xung quanh khi bạn muốn triển khai bộ đệm có thể tăng và thu hẹp với bộ nhớ khả dụng.


4
"Bộ đệm trong đó các giá trị được giữ trong WeakReferences sẽ khá vô dụng" Tôi hoàn toàn không đồng ý.
Thomas Eding

5
@trinithis - erm, tôi thực sự không biết phải nói gì. Tại sao một bộ đệm có giá trị biến mất ngay lúc bạn không tham chiếu chúng là một điều hữu ích ?
oxbow_lakes

4
Đối với một cái gì đó như ghi nhớ, một bộ đệm giữ lỏng lẻo các giá trị được lưu trong bộ nhớ cache của nó có thể hữu ích.
Thomas Eding

5
@ThomasEding tôi vẫn không hiểu. Lần duy nhất bộ đệm có vẻ hữu ích là khi không có tài liệu tham khảo nào khác ... Nếu bạn có tài liệu tham khảo về nó, bạn cần bộ đệm để làm gì?
Cruncher

2
@ThomasEding, Softref nói với môi trường "lưu trữ cái này cho đến khi bạn không còn bộ nhớ". Weakref nói với môi trường "lưu trữ cái này cho đến khi GC chạy". Thực sự không có trường hợp sử dụng nào cho yếu tố trừ khi bạn đang gỡ lỗi / cấu hình chính nó. Nếu bạn muốn bộ đệm nhạy cảm với bộ nhớ, hãy sử dụng softref. Nếu bạn không muốn có bộ đệm, thì đừng lưu nó vào bộ đệm! Trường hợp yếu refref đi vào?
Pacerier

30

Một cách sử dụng phổ biến của WeakReferences và WeakHashMaps nói riêng là để thêm các thuộc tính cho các đối tượng. Đôi khi bạn muốn thêm một số chức năng hoặc dữ liệu vào một đối tượng nhưng phân lớp và / hoặc thành phần không phải là một tùy chọn trong trường hợp đó, điều rõ ràng phải làm là tạo một hàm băm liên kết đối tượng bạn muốn mở rộng với thuộc tính bạn muốn thêm . sau đó bất cứ khi nào bạn cần tài sản, bạn chỉ cần tìm nó trên bản đồ. Tuy nhiên, nếu các đối tượng bạn đang thêm thuộc tính có xu hướng bị phá hủy và tạo ra rất nhiều, bạn có thể kết thúc với rất nhiều đối tượng cũ trong bản đồ của bạn chiếm rất nhiều bộ nhớ.

Nếu bạn sử dụng WeakHashMapthay vào đó, các đối tượng sẽ rời khỏi bản đồ của bạn ngay khi chúng không còn được sử dụng bởi phần còn lại của chương trình, đó là hành vi mong muốn.

Tôi phải làm điều này để thêm một số dữ liệu để java.awt.Componentcó được xung quanh một sự thay đổi trong JRE giữa 1.4.2 và 1.5, tôi có thể đã cố định nó bằng cách subclassing mỗi thành phần tôi đã quan tâm đến int ( JButton, JFrame, JPanel....) nhưng điều này đã được nhiều dễ dàng hơn với ít mã hơn.


1
Làm thế nào để WeakHashMap biết "chúng không còn được sử dụng bởi phần còn lại của chương trình của bạn"?
Vinoth Kumar CM

2
Một sơ đồ yếu sử dụng các tham chiếu yếu cho các khóa của nó. Khi một đối tượng chỉ được tham chiếu thông qua các tham chiếu yếu, trình thu gom rác sẽ 'thông báo' cho chủ sở hữu của tham chiếu yếu (trong trường hợp này là WeaHashMap). Tôi sẽ đọc trên WeakReferences và ReferenceQueues trong javadocs để hiểu cách các đối tượng này tương tác với trình thu gom rác.
luke

1
cảm ơn luke, bạn có thể cung cấp mã đơn giản cho bạn mô tả trên không?
đun sôi

Do đó, sự yếu kém chỉ có ý nghĩa trong Java do API kỳ quặc của Java nơi một số lớp không thể được mở rộng.
Pacerier

22

Một trường hợp hữu ích khác cho WeakHashMapWeakReferencelà một thực hiện đăng ký người nghe .

Khi bạn tạo một cái gì đó muốn nghe một số sự kiện nhất định, thông thường bạn đăng ký một người nghe, ví dụ

manager.registerListener(myListenerImpl);

Nếu managerlưu trữ trình nghe của bạn với a WeakReference, điều đó có nghĩa là bạn không cần phải xóa thanh ghi, ví dụ như manager.removeListener(myListenerImpl)bởi vì nó sẽ tự động bị xóa sau khi trình nghe hoặc thành phần của bạn giữ trình nghe không khả dụng.

Tất nhiên bạn vẫn có thể xóa trình nghe của mình theo cách thủ công, nhưng nếu bạn không hoặc bạn quên nó, nó sẽ không gây rò rỉ bộ nhớ và nó sẽ không ngăn người nghe của bạn bị thu gom rác.

Trường hợp nào WeakHashMapđi vào hình ảnh?

Sổ đăng ký người nghe muốn lưu trữ những người nghe đã đăng ký vì WeakReferencehọ cần một bộ sưu tập để lưu trữ các tài liệu tham khảo này. Không có WeakHashSettriển khai trong thư viện Java tiêu chuẩn, WeakHashMapnhưng chúng ta có thể dễ dàng sử dụng cái sau để "thực hiện" chức năng của cái đầu tiên:

Set<ListenerType> listenerSet =
    Collections.newSetFromMap(new WeakHashMap<ListenerType, Boolean>());

Với điều này listenerSetđể đăng ký một trình nghe mới, bạn chỉ cần thêm nó vào tập hợp và ngay cả khi nó không bị xóa rõ ràng, nếu trình nghe không còn được tham chiếu nữa, nó sẽ bị JVM xóa tự động.


10
Vấn đề với việc sử dụng yếuHashSets cho danh sách người nghe là các phiên bản trình nghe ẩn danh được tạo trong register () sẽ bị mất dễ dàng, điều này sẽ gây bất ngờ cho người dùng. Thay vào đó, an toàn hơn là giữ các tài liệu tham khảo mạnh mẽ hơn cho người nghe từ các nhà quản lý, và dựa vào người gọi để làm điều đúng đắn thay vào đó.
Gunanaresh

Đối với việc thực hiện đăng ký người nghe: Tất cả những người nghe đã đăng ký sẽ được thu thập / hủy trong lần đá tiếp theo? Ví dụ, trong khi bắn tất cả phương thức onS SomethingHappened () của người nghe, điều gì xảy ra nếu GC đá?
blackkara

@icza, tôi không thể tin rằng mọi người vẫn đang bán rong huyền thoại này. Đây là một câu trả lời hoàn toàn sai. Bạn cũng có thể nói một trường hợp hữu ích khác WeakHashMaplà bất cứ khi nào bạn cần một HashMapđối tượng. Vì vậy, wow bạn không phải tự làm hashmap.remove bao giờ vì các mục được tự động xóa sau khi obj nằm ngoài phạm vi! Nghĩa đen! Một hack ma thuật xấu xí như vậy là một facepalm hoàn chỉnh .
Pacerier

3
@Pacerier: Tôi đã theo dõi các liên kết của bạn từ các bình luận khác trong JavaScript ở đây và vẫn không hiểu tại sao việc thực hiện đăng ký người nghe với WeakMap là một huyền thoại. Ví dụ: nếu các máy khách WebSocket bị ràng buộc với một số trình nghe trên chúng thông qua dịch vụ đăng ký, thì có vẻ hợp lý khi lưu trữ các đối tượng ổ cắm dưới dạng các khóa trong WeakMap (để ngăn chúng bị treo trong bộ nhớ sau khi đóng kết nối, có lỗi) và có thể để lấy tất cả người nghe của họ nếu cần. Vì vậy, bạn có thể vui lòng cho biết, điều gì là chính xác sai với cách tiếp cận đó?
Hư hỏng hữu cơ

1
@Pacerier Tôi quá không hiểu sự phản đối của bạn. Trong kịch bản Xuất bản-Đăng ký hoặc Xe buýt Sự kiện, một tập hợp các tài liệu tham khảo yếu có ý nghĩa hoàn hảo với tôi. Đối tượng đăng ký được phép đi ra khỏi phạm vi và đi đến bộ sưu tập rác mà không cần phải hủy đăng ký chính thức. Quá trình hủy đăng ký đó có thể đặc biệt phức tạp nếu một đối tượng bên thứ ba chịu trách nhiệm cho đăng ký ban đầu mà không có kiến ​​thức của đối tượng đăng ký. Một bộ sưu tập WeakReferenceđơn giản hóa rất nhiều cơ sở mã và tránh các lỗi không cần thiết liên quan đến việc không đăng ký. Nhược điểm gì?
Basil Bourque

5

Bài đăng trên blog này cho thấy việc sử dụng cả hai lớp: Java: đồng bộ hóa trên ID . Việc sử dụng diễn ra như thế này:

private static IdMutexProvider MUTEX_PROVIDER = new IdMutexProvider();

public void performTask(String resourceId) {
    IdMutexProvider.Mutex mutext = MUTEX_PROVIDER.getMutex(resourceId);
    synchronized (mutext) {
        // look up the resource and do something with it
    }
}

IdMutextProvider cung cấp các đối tượng dựa trên id để đồng bộ hóa trên. Các yêu cầu là:

  • phải trả về một tham chiếu đến cùng một đối tượng để sử dụng đồng thời ID tương đương
  • phải trả về một đối tượng khác nhau cho các ID khác nhau
  • không có cơ chế phát hành (các đối tượng không được trả lại cho nhà cung cấp)
  • không được rò rỉ (các đối tượng không sử dụng đủ điều kiện để thu gom rác)

Điều này đạt được bằng cách sử dụng bản đồ lưu trữ nội bộ loại:

WeakHashMap<Mutex, WeakReference<Mutex>>

Đối tượng là cả khóa và giá trị. Khi không có gì bên ngoài bản đồ có một tham chiếu cứng đến đối tượng, nó có thể được thu gom rác. Các giá trị trong bản đồ được lưu trữ với các tham chiếu cứng, do đó, giá trị phải được bọc trong WeakReference để tránh rò rỉ bộ nhớ. Điểm cuối cùng này được bao phủ trong javadoc .


3

Nếu bạn muốn theo dõi tất cả các đối tượng được tạo của một lớp nhất định. Để vẫn cho phép các đối tượng này được thu gom rác, bạn giữ một danh sách / bản đồ các tham chiếu yếu đến các đối tượng thay vì chính các đối tượng.

Bây giờ nếu ai đó có thể giải thích các tham chiếu ảo cho tôi, tôi sẽ rất vui ...


2
Một lần sử dụng: PhantomReferences cho phép bạn xác định chính xác thời điểm một đối tượng bị xóa khỏi bộ nhớ. Thực tế họ là cách duy nhất để xác định điều đó. ( weblogs.java.net/blog/enicholas/archive/2006/05/ trên )
Jacob Krall

Trên thực tế, nó không bị xóa cho đến khi bạn xóa nó một cách rõ ràng. "Không giống như các tham chiếu mềm và yếu, các tham chiếu ảo không được xóa tự động bởi trình thu gom rác khi chúng bị mê hoặc. Một đối tượng có thể truy cập thông qua các tham chiếu ảo sẽ duy trì như vậy cho đến khi tất cả các tham chiếu đó bị xóa hoặc không thể truy cập được."
jontro

@jontro, nhưng nó đã được hoàn thiện , tất cả các thành viên đã biến mất. Thực tế, đó là một obj trống. Xem stackoverflow.com/q/7048767/632951
Pacerier

3

Như đã nêu ở trên, tham chiếu yếu được tổ chức miễn là tồn tại một tham chiếu mạnh.

Một ví dụ sử dụng sẽ là sử dụng WeakReference bên trong các trình nghe, để các trình nghe không còn hoạt động nữa khi tham chiếu chính đến đối tượng đích của chúng không còn nữa. Lưu ý rằng điều này không có nghĩa là WeakReference bị xóa khỏi danh sách người nghe, việc dọn dẹp vẫn là bắt buộc nhưng có thể được thực hiện, ví dụ, vào thời gian đã lên lịch. Điều này cũng có tác dụng ngăn chặn đối tượng được lắng nghe giữ các tài liệu tham khảo mạnh mẽ và cuối cùng là một nguồn gây ra sự phình to bộ nhớ. Ví dụ: Các thành phần GUI xoay giới thiệu một mô hình có vòng đời dài hơn cửa sổ.

Trong khi chơi với người nghe như được mô tả ở trên, chúng tôi nhanh chóng nhận ra rằng các vật thể được thu thập "ngay lập tức" từ quan điểm của người dùng.


Cảm ơn câu trả lời hữu ích. Nhưng tôi tự hỏi, trong trường hợp đó, người nghe có nên được đăng ký (tham khảo) mạnh mẽ không?
blackkara

Câu trả lời này là hoàn toàn sai. Xây dựng: stackoverflow.com/questions/154724/ từ
Pacerier

@Pacerier - cho WeakReferencesnhận xét của bạn là hoàn toàn sai!

2

Một ứng dụng trong thế giới thực mà tôi có cho WeakReferences là nếu bạn có một vật thể rất lớn mà hiếm khi được sử dụng. Bạn không muốn giữ nó trong bộ nhớ khi không cần thiết; nhưng, nếu một luồng khác cần cùng một đối tượng, bạn cũng không muốn hai trong số chúng trong bộ nhớ. Bạn có thể giữ một tham chiếu yếu đến đối tượng ở đâu đó và các tham chiếu cứng trong các phương thức sử dụng nó; khi cả hai phương thức kết thúc, đối tượng sẽ được thu thập.


1
Đây là một sự mềm mỏng, không phải là một sự yếu đuối. Xem stackoverflow.com/a/155492/632951
Pacerier


-1

bạn có thể sử dụng sơ đồ yếu để thực hiện bộ nhớ đệm không có tài nguyên để tạo đối tượng mở rộng.

nhưng lưu ý rằng không thể có các đối tượng có thể thay đổi. tôi đã sử dụng nó để lưu trữ kết quả truy vấn (mất khoảng 400 ms để thực thi) cho một công cụ tìm kiếm văn bản, điều này hiếm khi được cập nhật.


Bạn đang nói về một sự mềm mỏng, không phải là một sự yếu đuối. Xem stackoverflow.com/a/155492/632951
Pacerier
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.