java四種引用類型
從jdk1.2以來,java把對象的引用定義為四種級別,從而使程序可以更加靈活地控制對象的生命周期。四種引用類型按照由強到弱的順序分別為:強引用、軟引用、弱引用、虛引用。
強引用
public static void main(String[] args) throws IOException {
M m = new M();
m = null;
System.gc();
System.in.read();
}
m就是持有堆內(nèi)M對象的一個強引用,也是我們最常見的一種引用形式,屬于不可回收資源,垃圾回收期不會主動回收它。當內(nèi)存空間不足的時候,jvm會拋出OutOfMemoryError錯誤,也不會主動釋放強引用對象。
如果想釋放強引用對象,可以顯示的把引用復制為null,這樣jvm就會在合適的時間釋放掉強引用對象。
軟引用
SoftReference<byte[]> m = new SoftReference(new byte[1024 * 1024 * 10]);
System.out.println(m.get());
System.gc();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
byte[] b = new byte[1024 * 1024 * 15];
System.out.println(m.get() );
如果內(nèi)存空間足夠,垃圾回收器不會回收軟引用對象,如果內(nèi)存空間不足,垃圾回收器一定會回收軟引用的對象。
弱引用
public static void main(String[] args) {
WeakReference<M> m = new WeakReference<>(new M());
System.out.println(m.get()); // 返回對象
System.gc();
System.out.println(m.get()); // 返回null
}
弱引用有更短的生命周期,垃圾回收器只要掃描到弱引用對象,就會清除弱引用對象。
虛引用
private static final List<Object> LIST = new LinkedList<>();
private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>();
public static void main(String[] args) {
PhantomReference<M> phantomReference = new PhantomReference<>(new M(), QUEUE);
System.out.println(phantomReference.get());
new Thread(()->{
for (int i = 0; i < 30; i++) {
LIST.add(new byte[1024 * 1024]);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
System.out.println(phantomReference.get());
}
}).start();
new Thread(()->{
while (true) {
Reference<? extends M> poll = QUEUE.poll();
if (poll != null) {
System.out.println("虛引用對象被jvm回收了"+poll);
}
}
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
虛引用不影響對象的生命周期。虛引用需要和引用隊列相關(guān),當垃圾回收器準備回收一個對象,發(fā)現(xiàn)它還有虛引用,就會把虛引用加入到與之關(guān)聯(lián)的引用隊列中。
ThreadLocal與弱引用
ThreadLocal是什么
ThreadLocal是java.lang包中實現(xiàn)相同線程數(shù)據(jù)共享、不同線程數(shù)據(jù)隔離的工具。
static final ThreadLocal<String> tl = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+tl.get());
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
tl.set("hello");
}).start();
}
ThreadLocal和鎖之間的關(guān)系:鎖解決的是線程之間數(shù)據(jù)共享的問題,ThreadLocal出現(xiàn)的場景是線程之間沒有數(shù)據(jù)共享,只涉及到同一個線程不同位置使用數(shù)據(jù)的問題。
ThreadLocal實現(xiàn)原理
對象實例(對應value)和ThreadLocal對象實例(對應key)是由線程Thread來維護的。對象實例和ThreadLocal實例的映射關(guān)系存放在Thread對象中的一個map屬性中,這個map的類型為ThreadLocalMap。
由于ThreadLocalMap沒有對外暴露方法,想要修改或者獲取map中的值只能通過ThreadLocal的api來完成。如果Threadlocal對象銷毀了,那么就沒有辦法獲取到map中的值了。
// ThreadLocal的set方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
// 可以看到map是Thread對象的一個屬性
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 創(chuàng)建map,并賦值給Thread對象的一個屬性
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
所以涉及的值存儲的地方都是由ThreadLocalMap來完成的,ThreadLocalMap維護了ThreadLocal對象和實例對象的一個映射關(guān)系。
// ThreadLocalMap的set方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
ThreadLocalMap中使用Entry[]數(shù)組來維護鍵值對的映射關(guān)系,其中key為ThreadLocal對象,value為實例對象value。并且這里的key使用了弱引用,如果對應的ThreadLocal對象的強引用不存在了,這里面的ThreadLocal對象也會被清理掉。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
這樣設(shè)計的目的主要是:如果ThreadLocal對象已經(jīng)不在被強引用了,那么已經(jīng)沒有辦法再訪問到map中的值了,因為通過Thread對象的屬性訪問map,然后map再訪問value的路徑走不通(map的方法不對外暴露)。這個時候可以認為key已經(jīng)沒有用了。
那么為什么不把value也設(shè)置為弱引用呢?因為不清楚這個value除了map的引用是否還存在其他的引用。如果是弱引用,當其他地方value的強引用被清理掉之后,gc時就把value(只有弱引用)直接干掉了。Threadlocal對象作為的key還存在,而value卻不存在了,顯然是不合適的。
ThreadLocal與內(nèi)存泄露
當把ThreadLocal對象的強引用設(shè)置為null之后,就只剩下TheadLocalMap的key為弱引用類型,那么在下次gc的時候會回收掉ThreadLocal對象占用的空間,key就變成null了,而value還存在沒有被回收。而且由于map是Thread對象的一個屬性,只要Thread對象不銷毀,那么value的強引用就一直存在,這樣就導致了內(nèi)存泄露。如果Thread對象過一段時間就銷毀,那么影響不會特別大,如果是在線程池場景下,Thread對象一直不銷毀,就會導致value一直存在。
所以當線程中的某個ThreadLocal對象使用完了,需要立即調(diào)用remove方法,刪除Entry對象。