Understanding weak references in Java
Not long ago, I interviewed some candidates for senior Java development engineers. I often interview them and say, "Can you introduce me to some weak references in Java ?", If the subject says, "Well, is it related to garbage collection ?", I will be quite satisfied. I don't expect to answer this question as a descriptive article at the end of this article.
However, contrary to expectations, I was surprised to find that only two of the more than 20 applicants with an average of five years of development experience and high education background knew the existence of weak references, however, only one of them really understands this knowledge. During the interview, I also tried to remind me of some things to see if anyone suddenly said "It turned out to be this". The result was disappointing to me. I began to get confused. Why is this piece of knowledge so unvalued? After all, weak references are a very useful feature that was introduced seven years ago when Java 1.2 was released.
Well, I don't expect you to become a weak reference expert after reading this article, but I think you should at least understand what weak references are, how to use them, and what scenarios to use them. Since they are some unknown concepts, I will simply explain them to the first three questions.
Strong Reference)
Strong references are commonly used references, which are written as follows:
1 |
StringBuffer buffer = new StringBuffer(); |
A StringBuffer object is created above and the object is strongly referenced to the variable buffer. Yes, this is the operation of pediatrics. Please forgive me for saying so ). The most important thing about a Strong reference is that it can make the reference Strong), which determines its interaction with the garbage collector. Specifically, if an object can be reached through a strong reference Link (Strongly reachable), it will not be recycled. If you don't want the objects you are using to be recycled, this is exactly what you need.
However, strong references are so powerful
In a program, it is not uncommon to set a class to non-extensible. Of course, this can be fully implemented by marking the class as final. Or it can be more complicated, that is, an Interface is returned through the factory method that contains an unknown number of specific implementations through the internal package ). For example, we want to use a class called Widget, but this class cannot be inherited, so new functions cannot be added.
But what should we do if we want to track the additional information of the Widget object? Assume that we need to record the serial number of each object, but the Widget class does not contain this attribute and cannot be extended, so we cannot add this attribute. In fact, there is no problem at all. HashMap can completely solve the above problem.
1 |
serialNumberMap.put(widget, widgetSerialNumber); |
It seems that there is no problem, but the strong reference of the widget object is likely to cause problems. We can be sure that when a widget serial number is not required, we should remove this entry from the map. If we do not remove it, it may cause memory leakage, or we manually delete the widgets we are using, which may lead to the loss of valid data. In fact, these questions are very similar. This is a problem that is often encountered in the language management memory without the garbage collection mechanism. But we don't have to worry about this, because we use the Java language with the garbage collection mechanism.
Another issue that may be caused by strong references is caching, especially the caching of large files such as images. Assume that you have a program that needs to process images provided by users. The common practice is to cache image data because loading images from disks is costly, at the same time, we also want to avoid two identical image data copies in the memory.
The purpose of caching is to avoid loading unnecessary files again. You will soon find that the cache will always contain a reference that has pointed to the image data in the memory. The use of strong references will force the image data to stay in the memory, which requires you to decide when the image data is not required and manually removed from the cache, so that the garbage collector can be recycled. Therefore, once again, you are forced to do the work that the garbage collector should do, and manually decide which object to clean.
Weak Reference)
In short, weak references are not so powerful references that keep objects in memory. With WeakReference, the garbage collector will help you decide when to recycle the referenced object and remove the object from the memory. Create weak references as follows
1 |
WeakReference<Widget> weakWidget = new WeakReference<Widget>(widget); |
Use weakWidget. get () can get the real Widget object, because the weak reference cannot block the garbage collector from recycling it, you will find that when there is no strong reference to the widget object) when get is used, null is suddenly returned.
To solve the problem of the number of widget sequence records, the simplest method is to use the WeakHashMap class built in Java. WeakHashMap is almost the same as HashMap. The only difference is that its key is not a value !!!) Use WeakReference. When the WeakHashMap key is marked as spam, the corresponding entries of this key are automatically removed. This avoids the manual deletion of widgets that are not needed above. Using WeakHashMap can be easily converted to HashMap or Map.
Reference Queue)
Once the weak reference object starts to return null, the object pointed to by the weak reference will be marked as garbage. And this weak referenced object is not the object it points to) is useless. Usually some cleanup work is required at this time. For example, WeakHashMap will remove useless entries at this time to avoid saving meaningless weak references with unlimited growth.
The reference queue can easily implement reference that is not required by the trail. When you construct a WeakReference, you pass in a ReferenceQueue object. When the object pointed to by the reference is marked as spam, the referenced object is automatically added to the reference queue. Next, you can process the incoming reference queue at a fixed cycle, such as cleaning up the useless reference objects.
Four types of references
In Java, there are actually four types of references with different strengths, from strong to weak. They are strong references, soft references, weak references, and virtual references. The above section describes strong references and weak references. The following describes the remaining two, soft references and virtual references.
Soft Reference)
Soft references are basically the same as weak references, but they are more powerful than weak references, which prevent the garbage collection period from recycling the objects it points. If a weak reference is reachable, the object will be destroyed by the garbage collector in the next recycling cycle. However, if soft references are available, the object will stay in the memory for a longer period of time. When the memory is insufficient, the garbage collector recycles the objects that can be reached by these soft references.
Since the accessible objects of soft references are longer than the objects that can be achieved by weak references in memory, we can use this feature for caching. In this way, you can save a lot of things. The Garbage Collector will care about the current accessible type and the amount of memory consumed for processing.
Virtual Reference Phantom Reference)
Unlike soft references and weak references, the objects pointed to by virtual references are very fragile. We cannot use the get method to obtain the objects pointed to by virtual references. Its only function is to add itself to the reference queue after the object it points to is recycled. It is used to record that the object pointed to by the reference has been destroyed.
When a weak reference pointing to an object becomes reachable, the weak reference will be added to the reference queue. This operation occurs before the object structure or garbage collection. Theoretically, this forthcoming recycled pair can be revived in a non-conforming destructor. However, this weak reference will be destroyed. Virtual references will be added to the reference queue only after the objects it points to are removed from the memory. Its get method always returns null to prevent the almost destroyed objects from being revived.
There are two main scenarios for virtual references. It allows you to know when the referenced object is removed from the memory. In fact, this is the only method in Java. This is especially manifested in processing large files like images. When you determine that an image data object should be recycled, you can use the virtual index to determine whether the object will be recycled and then continue to load the next image. In this way, we can avoid terrible memory overflow errors as much as possible.
Second, virtual references can avoid many issues during analysis. The finalize method allows you to create a strong reference pointing to objects that are quickly destroyed to restore these objects. However, an object that overwrites the finalize method needs to go through two separate garbage collection cycles if it is to be recycled. In the first cycle, an object is marked as recycled for analysis. However, this object may be revived due to the weak possibility during the structure analysis. In this case, the garbage collector needs to run again before the object is actually destroyed. Because the Destructor may not be timely, you need to go through an uncertain garbage collection cycle before calling the object's destructor. This means that a great delay may occur when the object is actually cleared. This is why an annoying memory overflow error occurs when most of the stacks are marked as garbage.
When virtual references are used, the above conditions will be solved. When a Virtual Reference is added to the reference queue, there is absolutely no way to get a destroyed object. At this time, the object has been destroyed from the memory. Because Virtual references cannot be used to regenerate objects that point to them, their objects will be cleared in the first cycle of garbage collection.
It is obvious that the finalize method is not recommended to be overwritten. Because of the obvious security and efficiency of virtual references, removing the finalize method can significantly simplify virtual machines. Of course, you can also rewrite this method to achieve more. This depends on your choice.
Summary
I want to see that many people are beginning to complain. Why do you want to talk about an old-fashioned API for the past decade? Well, in my experience, many Java programmers do not understand this knowledge very well. I think it is necessary to have some in-depth understanding. At the same time, I hope you can get something from this article.