Deep understanding of weak references in Java

  • 2020-04-01 03:40:43
  • OfStack

Not long ago, I interviewed candidates for a position as a senior Java development engineer. I often interview them and say, "can you introduce me to some weak references in Java?" "If the interviewer says something like," well, is it about recycling? I would be basically satisfied, and I don't expect the answer to be a treatise that examines the book.

Instead, I was surprised to find that out of nearly 20 applicants with an average of 5 years of development experience and an advanced degree background, only two knew about weak references, but only one of them really knew anything about them. During the interview, I also tried to suggest a few things to see if anyone would suddenly say, "that's it." I was disappointed. I'm beginning to wonder why this knowledge is so underappreciated, since weak references are a useful feature that was introduced seven years ago with the release of Java 1.2.

Well, I don't expect you to be an expert on weak references after reading this article, but I think you should at least know what weak references are, how to use them, and what scenarios to use them in. Since they are some unknown concepts, I will briefly address the three questions above.

Strong Reference

A strong reference is a commonly used reference written as follows:


StringBuffer buffer = new StringBuffer();

A StringBuffer object is created above and the (strong) reference of this object is stored in the variable buffer. Yes, that's the pediatric operation (forgive me for saying that). The most important thing about a Strong reference is that it makes the reference Strong, which determines its interaction with the garbage collector. In particular, if an object can be reached by a string of strong reference link (Strongly reachable), it will not be recycled. This is exactly what you need if you don't want the object you're using to be recycled.

But strong references are so strong

It is somewhat unusual for a class to be made unextensible ina program, but this can be marked as final. Or it could be more complicated by returning an Interface through an internal factory method that contains an unknown number of specific implementations. For example, we want to use a class called Widget, but the class cannot be inherited, so we cannot add new functionality.

But what if we want to keep track of additional information about the Widget object? Suppose we need to record the sequence number of each object, but since the Widget class does not contain this property and cannot be extended, we cannot add this property. There is no problem at all, and HashMap is the perfect solution.


serialNumberMap.put(widget, widgetSerialNumber);

This may seem fine on the surface, but strong references to widget objects are likely to cause problems. We can be sure that when a widget sequence number is not needed, we should remove the entry from the map. If we didn't remove it, it could result in a memory leak, or if we manually removed the widgets we were using, which could result in the loss of valid data. In fact, these problems are very similar, which is a common problem in memory management of languages without garbage collection mechanism. But we don't have to worry about that because we're using the Java language with the garbage collection mechanism.

Another problem with strong references is caching, especially for large files like images. Let's say you have a program that handles user-supplied images. The usual way to do this is to cache the image data, because loading images from disk is expensive and we want to avoid having two copies of the same image in memory at the same time.

The purpose of the cache is to prevent us from reloading unwanted files. You will soon notice that the cache will always contain a reference to the in-memory image data. Using strong references forces the image data to remain in memory, which requires you to decide when the image data is not needed and manually removed from the cache so that the garbage collector can recover it. So once again you are forced to do the garbage collector's job and manually decide which object to clean.

Weak Reference

A weak reference is simply a reference that doesn't have the same ability to keep an object in memory. Using a WeakReference, the garbage collector will help you decide when to retrieve the referenced object and remove it from memory. Create a weak reference as follows:


eakReference<Widget> weakWidget = new WeakReference<Widget>(widget);

Use weakwidget.get () to get the real Widget object, because a weak reference does not prevent the garbage collector from collecting it, and you will find that when you use get(when there are no strong references to the Widget object) it will suddenly return null.

The easiest way to solve the widget sequence number record problem above is to use the WeakHashMap class built into Java. WeakHashMap is almost the same as HashMap, the only difference being its key (not the value!!). Use a WeakReference reference. When the key of a WeakHashMap is marked as garbage, the entry corresponding to that key is automatically removed. This avoids the problem of manually deleting unwanted Widget objects above. A WeakHashMap can be easily converted to a HashMap or Map using a WeakHashMap.

Reference Queue

Once a weak reference object starts returning null, the object to which the weak reference refers is marked as garbage. This weak reference object (which is not the object to which it points) is of little use. This is usually the time for some cleanup. For example, a WeakHashMap removes useless entries at this point to avoid storing unlimitedly growing weak references.

Reference queues make it easy to keep track of unwanted references. When you pass in a ReferenceQueue object when constructing a WeakReference, when the object the reference points to is marked as garbage, the ReferenceQueue object is automatically added to the ReferenceQueue. Next, you can handle the incoming reference queue for a fixed period of time, such as doing some cleanup to handle the unused reference objects.

Four types of references

There are actually four types of strong to weak references in Java: strong, soft, weak, and virtual. The above section covers strong and weak references, and the next two, soft and virtual references.

Soft Reference

A soft reference is basically the same as a weak reference, except that it is more powerful than a weak reference in preventing the garbage collector from collecting the object it points to. If an object is reachable by a weak reference, the object is destroyed by the garbage collector for the next collection cycle. But if a soft reference is reachable, the object will stay in memory longer. Only when memory is low will the garbage collector recycle the reachable soft references.

Since soft reference reachable objects stay in memory longer than weak reference reachable objects, we can take advantage of this feature for caching. In this way, you can save a lot of things, and the garbage collector will care about the current reachable types and memory consumption.

Phantom Reference

Unlike soft and weak references, virtual references point to fragile objects, we can not get the object it points to through the get method. Its only function is that when the object it points to is retrieved, it is added to the reference queue to record that the object it points to has been destroyed.

When a weak reference to an object becomes reachable by a weak reference, the weak reference is added to the reference queue. This occurs before object destruction or garbage collection actually occurs. In theory, the object to be reclaimed can be resurrected in a destructive method that does not conform to the specification. But the weak reference will be destroyed. A virtual reference is added to the reference queue only after the object it points to is removed from memory. The get method always returns null to prevent the nearly destroyed object it points to from being resurrected.

There are two main scenarios for virtual reference usage. It allows you to know exactly when an object it references is removed from memory. This is actually the only way in Java. This is especially true when working with large files like pictures. When you determine that an image data object should be recycled, you can use a virtual reference to determine that the object is recycled and then proceed to load the next image. This avoids the dreaded memory overflow error as much as possible.

Second, virtual references can avoid many destruction-related problems. Finalize methods can resurrect objects by creating strong references to them that are about to be destroyed. However, an object that overrides the finalize method needs to go through two separate garbage collection cycles if it wants to be recycled. In the first cycle, an object is marked recyclable so that it can be destructed. But there is still a slight chance that the object will be resurrected during the destruction. In this case, the garbage collector needs to run again before the object is actually destroyed. Because destructions may not be timely, an indefinite number of garbage collection cycles are required before an object's destructions can be invoked. This means that there may be a significant delay in actually cleaning up the object. This is why annoying out-of-memory errors still occur when most of the heap is marked as garbage.

With virtual references, the above situation is solved. When a virtual reference is added to the reference queue, you can never get a destroyed object. Because at this point, the object has been destroyed from memory. Because a virtual reference cannot be used to regenerate the object it points to, its object will be cleaned up in the first cycle of garbage collection.

Clearly, finalize methods are not recommended to be overridden. Because virtual references are obviously safe and efficient, the finalize method can make the virtual machine significantly easier to remove. You can also override this method to do more. It's all about personal choice.

conclusion

Well, in my experience, a lot of Java programmers don't know much about this, and I think it's important to have some insight into it, and I hope you'll get something out of this.


Related articles: