java 中的引用类型
GC基本原理
GC (Garbage Collection)的基本原理:將內存中不再被使用的對象進行回收,GC中用于回收的方法稱為收集器,由于GC需要消耗一些資源和時間,Java在對對象的生命周期特征進行分析后,按照新生代、舊生代的方式來對對象進行收集,以盡可能的縮短GC對應用造成的暫停
(1)對新生代的對象的收集稱為minor GC;
(2)對舊生代的對象的收集稱為Full GC;
(3)程序中主動調用System.gc()強制執行的GC為Full GC。
不同的對象引用類型, GC會采用不同的方法進行回收,JVM對象的引用分為了四種類型:
(1)強引用:默認情況下,對象采用的均為強引用(這個對象的實例沒有其他對象引用,GC時才會被回收)
(2)軟引用:軟引用是Java中提供的一種比較適合于緩存場景的應用(只有在內存不夠用的情況下才會被GC)
(3)弱引用:在GC時一定會被GC回收
(4)虛引用:由于虛引用只是用來得知對象是否被GC
強引用
強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器寧愿拋出OOM(OutOfMemoryError)也不會回收它。
說明
不要被這個強字嚇到,以為這個引用就很厲害,其實強引用就是程序中使用的一般引用類型。舉個簡單的例子:
String s = new String("Hello world!");
強可達
如果一個對象與GC Roots之間存在強引用,則稱這個對象為強可達(strong reachable)對象。
當你聲明一個變量并指向一個實例的時候,其實就是在創造一個強引用。
那么,既然叫強引用,它“強”在哪里呢?
這主要體現在JVM進行GC的時候,只要對象有強引用與其關聯,就絕對不會對它進行回收,即使已經內存不足了也不會收回有強引用指向的對象。
如果你不需要使用某個對象了,可以將相應的引用設置為null,消除強引用來幫助垃圾回收器進行回收。因為過多的強引用也是導致OOM的罪魁禍首。
s = null;
顯式地設置消除引用,或已超出對象的生命周期范圍,則JVM會認為該對象不存在引用,這時就可能會回收這個對象。但是具體什么時候收集這要取決于具體的GC算法。
如果在一個方法的內部有一個變量s持有一個對象(Object)的強引用,那么這個變量s保存在棧中,而真正的引用內容(object)保存在堆中。當這個方法運行完成后就會退出方法棧,則引用s也會被銷毀,這個object就可能會在之后的一次GC中回收。但是當這個s是全局變量時,就需要在不再使用這個對象時將引用s賦值為null,也就是消除與object對象之間的強引用,因為有強引用關聯的對象是不會被垃圾回收的。
下面看另一個例子:
A a = new A();
B b = new B(a);
a = null;
這里a和b都持有一個對象的強引用,當執行 a = null 時, a 不再持有 A 的強引用。講道理,A 已經該被回收了。但是這里a = null 時,A 對象不滿足被回收的條件,因為還有一個B對象持有其強引用,這時候就會造成內存泄漏。
再看另一個會導致內存泄漏的例子:
public static ArrayList<Object> list = new ArrayList<Object>();
public void stackOverflowTest(Object object){
list.add(object);
object = null;
}
GC回收的是不可達對象,但是,在這個靜態集合類對象中,持有了對象的強引用,但卻有可能其中的某些對象已經不再使用了,所以當非靜態對象被靜態變量持有強引用的時候,最容易發生內存泄露。
在方法中從list獲取到對象后賦值給一個變量,使用完之后將這個變量設置為null并不會釋放object引用的對象,因為list中還是持有對象的強引用。這時就造成了內存泄漏。
小結
所以小結一下強引用的特點:
- 強引用就是最普通的引用
- 可以使用強引用直接訪問目標對象
- 強引用指向的對象在任何時候都不會被系統回收
- 強引用可能會導致內存泄漏
- 過多的強引用會導致OOM
軟引用
軟引用是使用SoftReference創建的引用,強度弱于強引用,被其引用的對象在內存不足的時候會被回收,不會產生內存溢出。
說明
軟引用,顧名思義就是比較“軟”一點的引用。
當一個對象與GC Roots之間存在強引用時,無論何時都不會被GC回收掉。如果一個對象與GC Roots之間沒有強引用與其關聯而存在軟引用關聯時,那么垃圾回收器對它的態度就取決于內存的緊張程度了。如果內存空間足夠,垃圾回收器就不會回收這個對象,但如果內存空間不足了,它就難逃被回收的厄運。
軟可達
如果一個對象與GC Roots之間不存在強引用,但是存在軟引用,則稱這個對象為軟可達(soft reachable)對象。
在垃圾回收器沒有回收它的時候,軟可達對象就像強可達對象一樣,可以被程序正常訪問和使用,但是需要通過軟引用對象間接訪問,需要的話也能重新使用強引用將其關聯。所以軟引用適合用來做內存敏感的高速緩存。
String s = new String("hello world"); // 創建強引用與String對象關聯,現在該String對象為強可達狀態
SoftReference<String> softRef = new SoftReference<String>(s); // 再創建一個軟引用關聯該對象
s = null; // 消除強引用,現在只剩下軟引用與其關聯,該String對象為軟可達狀態
s = softRef.get(); // 重新關聯上強引用
這里變量s持有對字符串對象的強引用,而softRef持有對該對象的軟引用,所以當執行s = null后,字符串對象就只剩下軟引用了,這時如果因為內存不足發生Full GC,就會把這個字符串對象回收掉。
注意
在垃圾回收器回收一個對象前,SoftReference類所提供的get方法會返回Java對象的強引用,一旦垃圾線程回收該對象之后,get方法將返回null。所以在獲取軟引用對象的代碼中,一定要先判斷返回是否為null,以免出現NullPointerException異常而導致應用崩潰。
下面的代碼會讓s再次持有對象的強引用:
s = softRef.get();
如果在softRef指向的對象被回收前,用強引用指向該對象,那這個對象又會變成強可達。
來看一個使用SoftReference的例子:
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
public class TestA {
static class OOMClass{
private int[] oom = new int[1024 * 100];// 100KB
}
public static void main(String[] args) throws InterruptedException {
List<SoftReference> list = new ArrayList<>();
while(true){
for (int i = 0; i < 100; i++) {
list.add(new SoftReference<OOMClass>(new OOMClass()));
}
Thread.sleep(500);
}
}
}
設置一下虛擬機參數:
-verbose:gc -Xms4m -Xmx4m -Xmn2m
運行結果:
[GC (Allocation Failure) 1017K->432K(3584K), 0.0017239 secs]
[GC (Allocation Failure) 1072K->472K(3584K), 0.0099237 secs]
[GC (Allocation Failure) 1323K->1296K(3584K), 0.0009528 secs]
[GC (Allocation Failure) 2114K->2136K(3584K), 0.0009951 secs]
[Full GC (Ergonomics) 2136K->1992K(3584K), 0.0040658 secs]
[Full GC (Ergonomics) 2807K->2791K(3584K), 0.0036280 secs]
[Full GC (Allocation Failure) 2791K->373K(3584K), 0.0032477 secs]
[Full GC (Ergonomics) 2786K->2773K(3584K), 0.0034554 secs]
[Full GC (Allocation Failure) 2773K->373K(3584K), 0.0032667 secs]
[Full GC (Ergonomics) 2798K->2775K(3584K), 0.0036231 secs]
[Full GC (Allocation Failure) 2775K->375K(3584K), 0.0055482 secs]
[Full GC (Ergonomics) 2799K->2776K(3584K), 0.0031358 secs]
...省略n次GC信息
在TestA中,我們使用死循環不斷的往list中添加新對象,如果是強引用,會很快因為內存不足而拋出OOM,因為這里的堆內存大小設置為了4M,而一個對象就有100KB,一個循環添加100個對象,也就是差不多10M,顯然一個循環都跑不完就會內存不足,而這里,因為使用的是軟引用,所以JVM會在內存不足的時候將軟引用回收掉。
[Full GC (Allocation Failure) 2791K->373K(3584K), 0.0032477 secs]
從這一條可以看出,在內存不足發生Full GC時,回收掉了大部分的軟引用指向的對象,釋放了大量的內存。
因為這里新生代只分配了2M,所以很快就會發生GC,如果你的程序運行沒有看到這個結果,請先確認一下虛擬機參數是否設置正確,如果設置正確還是沒有看到,那么將循環次數由1000改為10000或者100000在試試看。
應用場景
軟引用關聯的對象,只有在內存不足的時候JVM才會回收該對象。這一點可以很好地用來解決OOM的問題,并且這個特性很適合用來實現緩存:比如網頁緩存、圖片緩存等。
現在考慮這樣一個場景 ,在很多應用中,都會出現大量的默認圖片,比如說QQ的默認頭像,應用內的默認圖標等等,這些圖片很多地方會用到。
如果每次都去讀取圖片,由于讀取文件速度較慢,大量重復的讀取會導致性能下降。所以可以考慮將圖片緩存起來,需要的時候直接從內存中讀取。但是,由于圖片占用內存空間比較大,緩存的圖片過多會占用比較多的內存,就可能比較容易發生OOM。這時候,軟引用就派得上用場了。
注意
SoftReference對象是用來保存軟引用的,但它同時也是一個Java對象。所以,當軟可及對象被回收之后,雖然這個SoftReference對象的get()方法返回null,但SoftReference對象本身并不是null,而此時這個SoftReference對象已經不再具有存在的價值,需要一個適當的清除機制,避免大量SoftReference對象帶來的內存泄漏。
ReferenceQueue就是用來保存這些需要被清理的引用對象的。軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
下面用SoftReference來實現一個簡單的緩存類:
public class SoftCache<T> {
// 引用隊列
private ReferenceQueue<T> referenceQueue = new ReferenceQueue<>();
// 保存軟引用集合,在引用對象被回收后銷毀
private List<Reference<T>> list = new ArrayList<>();
// 添加緩存對象
public synchronized void add(T obj){
// 構建軟引用
Reference<T> reference = new SoftReference<T>(obj, referenceQueue);
// 加入列表中
list.add(reference);
}
// 獲取緩存對象
public synchronized T get(int index){
// 先對無效引用進行清理
clear();
if (index < 0 || list.size() < index){
return null;
}
Reference<T> reference = list.get(index);
return reference == null ? null : reference.get();
}
public int size(){
return list.size();
}
@SuppressWarnings("unchecked")
private void clear(){
Reference<T> reference;
while (null != (reference = (Reference<T>) referenceQueue.poll())){
list.remove(reference);
}
}
}
然后測試一下這個緩存類:
public class SoftCacheTest {
private static int num = 0;
public static void main(String[] args){
SoftCache<OOMClass> softCache = new SoftCache<>();
for (int i = 0; i < 40; i++) {
softCache.add(new OOMClass("OOM Obj-" + ++num));
}
System.out.println(softCache.size());
for (int i = 0; i < softCache.size(); i++) {
OOMClass obj = softCache.get(i);
System.out.println(obj == null ? "null" : obj.name);
}
System.out.println(softCache.size());
}
static class OOMClass{
private String name;
private int[] oom = new int[1024 * 100];// 100KB
public OOMClass(String name) {
this.name = name;
}
}
}
仍使用之前的虛擬機參數:
-verbose:gc -Xms4m -Xmx4m -Xmn2m
運行結果:
[GC (Allocation Failure) 1017K->432K(3584K), 0.0012236 secs]
[GC (Allocation Failure) 1117K->496K(3584K), 0.0016875 secs]
[GC (Allocation Failure) 1347K->1229K(3584K), 0.0015059 secs]
[GC (Allocation Failure) 2047K->2125K(3584K), 0.0018090 secs]
[Full GC (Ergonomics) 2125K->1994K(3584K), 0.0054759 secs]
[Full GC (Ergonomics) 2822K->2794K(3584K), 0.0023167 secs]
[Full GC (Allocation Failure) 2794K->376K(3584K), 0.0036056 secs]
[Full GC (Ergonomics) 2795K->2776K(3584K), 0.0042365 secs]
[Full GC (Allocation Failure) 2776K->376K(3584K), 0.0035122 secs]
[Full GC (Ergonomics) 2795K->2776K(3584K), 0.0054760 secs]
[Full GC (Allocation Failure) 2776K->376K(3584K), 0.0036965 secs]
[Full GC (Ergonomics) 2802K->2777K(3584K), 0.0044513 secs]
[Full GC (Allocation Failure) 2777K->376K(3584K), 0.0041400 secs]
[Full GC (Ergonomics) 2796K->2777K(3584K), 0.0025255 secs]
[Full GC (Allocation Failure) 2777K->376K(3584K), 0.0037690 secs]
[Full GC (Ergonomics) 2817K->2777K(3584K), 0.0037759 secs]
[Full GC (Allocation Failure) 2777K->377K(3584K), 0.0042416 secs]
緩存列表大小:40
OOM Obj-37
OOM Obj-38
OOM Obj-39
OOM Obj-40
緩存列表大小:4
可以看到,緩存40個軟引用對象之后,如果一次性全部存儲,顯然內存大小無法滿足,所以在不斷創建軟引用對象的過程中,不斷發生GC來進行垃圾回收,最終只有4個軟引用未被清理掉。
強引用與軟引用對比
沒有對比就沒有傷害,來將強引用和軟引用對比一下:
public class Test {
static class OOMClass{
private int[] oom = new int[1024];
}
public static void main(String[] args) {
testStrongReference();
//testSoftReference();
}
public static void testStrongReference(){
List<OOMClass> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(new OOMClass());
}
}
public static void testSoftReference(){
ReferenceQueue<OOMClass> referenceQueue = new ReferenceQueue<>();
List<SoftReference> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
OOMClass oomClass = new OOMClass();
list.add(new SoftReference(oomClass, referenceQueue));
oomClass = null;
}
}
}
運行testStrongReference方法的結果如下:
[GC (Allocation Failure) 1019K->384K(3584K), 0.0033595 secs]
[GC (Allocation Failure) 1406K->856K(3584K), 0.0013098 secs]
[GC (Allocation Failure) 1880K->1836K(3584K), 0.0014382 secs]
[Full GC (Ergonomics) 1836K->1756K(3584K), 0.0039761 secs]
[Full GC (Ergonomics) 2778K->2758K(3584K), 0.0021269 secs]
[Full GC (Ergonomics) 2779K->2770K(3584K), 0.0016329 secs]
[Full GC (Ergonomics) 2779K->2775K(3584K), 0.0023157 secs]
[Full GC (Ergonomics) 2775K->2775K(3584K), 0.0015927 secs]
[Full GC (Ergonomics) 3037K->3029K(3584K), 0.0025071 secs]
[Full GC (Ergonomics) 3067K->3065K(3584K), 0.0017529 secs]
[Full GC (Allocation Failure) 3065K->3047K(3584K), 0.0033445 secs]
[Full GC (Ergonomics) 3068K->3059K(3584K), 0.0016623 secs]
[Full GC (Ergonomics) 3070K->3068K(3584K), 0.0028357 secs]
[Full GC (Allocation Failure) 3068K->3068K(3584K), 0.0017616 secs]
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid3352.hprof ...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Heap dump file created [3855956 bytes in 0.017 secs]
[Full GC (Ergonomics) 3071K->376K(3584K), 0.0032068 secs]
at reference.Test$OOMClass.<init>(Test.java:11)
at reference.Test.testStrongReference(Test.java:22)
at reference.Test.main(Test.java:15)
Process finished with exit code 1
可以看到,很快就拋出了OOM,原因是Java heap space,也就是堆內存不足。
如果運行testSoftReference方法,將會得到如下結果:
[GC (Allocation Failure) 1019K->464K(3584K), 0.0019850 secs]
[GC (Allocation Failure) 1484K->844K(3584K), 0.0015920 secs]
[GC (Allocation Failure) 1868K->1860K(3584K), 0.0043236 secs]
[Full GC (Ergonomics) 1860K->1781K(3584K), 0.0044581 secs]
[Full GC (Ergonomics) 2802K->2754K(3584K), 0.0041726 secs]
[Full GC (Ergonomics) 2802K->2799K(3584K), 0.0031293 secs]
[Full GC (Ergonomics) 3023K->3023K(3584K), 0.0024830 secs]
[Full GC (Ergonomics) 3071K->3068K(3584K), 0.0035025 secs]
[Full GC (Allocation Failure) 3068K->405K(3584K), 0.0040672 secs]
[GC (Allocation Failure) 1512K->1567K(3584K), 0.0011170 secs]
[Full GC (Ergonomics) 1567K->1496K(3584K), 0.0048438 secs]
可以看到,并沒有拋出OOM,而是進行多次了GC,可以明顯的看到這一條:
[Full GC (Allocation Failure) 3068K->405K(3584K), 0.0040672 secs]
當內存不足時進行了一次Full GC,回收了大部分內存空間,也就是將大部分軟引用指向的對象回收掉了。
小結
- 軟引用弱于強引用
- 軟引用指向的對象會在內存不足時被垃圾回收清理掉
- JVM會優先回收長時間閑置不用的軟引用對象,對那些剛剛構建的或剛剛使用過的軟引用對象- 會盡可能保留
- 軟引用可以有效的解決OOM問題
- 軟引用適合用作非必須大對象的緩存
總結
以上是生活随笔為你收集整理的java 中的引用类型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: javascript笔记06:类的创建
- 下一篇: 因以下文件损坏或丢失windows-(因