一、java中四種引用類型
- 強引用 NormalReference(一個普通變量指向一個對象,引用消失以后,對象就會被GC)
Object o = new Object() - 軟引用 SoftReference(有一個軟引用對象,軟引用對象中有個引用指向一個對象,這個對象是被軟引用連著的,在GC的時候會被特殊處理,堆內(nèi)存不夠用的時候就會被回收)
/**
* -Xmx30M 設(shè)置最大堆內(nèi)存為30M
*/
public class SoftReferenceDemo {
public static void main(String[] args) {
SoftReference<byte[]> m = new SoftReference<byte[]>(new byte[1024*1024*10]);
System.out.println(m.get());
System.gc();
try {
Thread.sleep(500);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(m.get());
// 再分配一個數(shù)組,占據(jù)堆內(nèi)存空間的超過65%=10M,就會為null,小于65%比如9M則為對象地址
// 如果直接和上面的對象占用堆內(nèi)存大小加起來大于30M則java.lang.OutOfMemoryError: Java heap space
byte[] b = new byte[1024*1024*10];
System.out.println(m.get());
}
}
- 弱引用 WeakReference(有一個弱引用對象,弱引用對象中有個引用指向一個對象,這個對象只要遇到GC就會被回收)弱引用作用是解決內(nèi)存泄露
public class WeakReferenceDemo {
public static void main(String[] args) {
WeakReference<Person> m=new WeakReference<Person>(new Person());
System.out.println(m.get());
System.gc();
System.out.println(m.get());
}
}
- 虛引用 PhantomReference(有跟沒有差不多,永遠(yuǎn)get不到,作用是管理堆外內(nèi)存)
二、ThreadLocal
threadLocal就是一個容器,A線程只能拿到自己放入threadLocal的東西,拿不到B線程放進去的東西
使用案例
不使用threadLocal
public class ThreadLocalDemo {
volatile static Person p = new Person();
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(p.name);
}
}).start();
new Thread(new Runnable() {
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
p.name="lisi";
}
}).start();
}
}
class Person{
String name="zhangsan";
}
上述打印結(jié)果lisi
使用threadLocal以后
public class ThreadLocalDemo {
// volatile static Person p = new Person();
static ThreadLocal<Person> t1= new ThreadLocal<Person>();
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t1.get());
}
}).start();
new Thread(new Runnable() {
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.set(new Person());
}
}).start();
}
}
class Person{
String name="zhangsan";
}
打印結(jié)果為null
源碼解析

java.lang.ThreadLocal #set
public void set(T value) {
//獲取當(dāng)前線程
Thread t = Thread.currentThread();
//獲取當(dāng)前線程的本地map
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
java.lang.ThreadLocal.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;
}
}
// new一個虛引用
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
注意:類似于hashmap7的結(jié)構(gòu)是一個entry數(shù)組,entry是鏈表節(jié)點,下面也是new一個entry鍵值對作為threadLocalMap的結(jié)構(gòu)數(shù)組中成員
java.lang.ThreadLocal.ThreadLocalMap
static class ThreadLocalMap {
// 這個Entry是一個weakReference
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
// 調(diào)用子類的構(gòu)造方法的時候,會先去實例化父類
// 下面這句等效于 new WeakReference(new K()),即k對象是被弱引用指向的
super(k);
value = v;
}
}
}
java.lang.ThreadLocal.ThreadLocalMap #getMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
獲取的是當(dāng)前線程里面的threadlocalmap,自然B線程無法獲取A線程放置的內(nèi)容,threadLocal最明顯的使用時spring中的事務(wù)@Transaction
流程:new 一個threadLocal,然后調(diào)用set方法,引用了當(dāng)前線程的threadLocalMap,然后創(chuàng)建一個Entry對象即弱引用對象,讓該弱引用對象指向new的threadLocal對象這個key,然后就將這個Entry放到threadLocalMap中
面試題1 為什么Entry要用弱引用
下面代碼中的這個threadLocal對象,有2個地方引用它
- 一個是強引用 ThreadLocal<Person> t1;
- 還有一個是線程的threadLocalMap的一個Entry鍵值對的key也指向他,并且這個指向是一個弱引用,弱指向
ThreadLocal<Person> t1=new ThreadLocal<>();
r1.set(new Person());
t1.remove();
內(nèi)存泄露場景1:
假設(shè)Entry為強引用,因為是強引用,當(dāng)我們寫t1=null的時候(或者main方法退出),t1不再使用的時候,這個new出來的threadLocal應(yīng)該被回收掉,可是因為在t1中set了一個new Person(),則ThreadLocalMap中仍然有個entry的key指向這個ThreadLocal對象t1,因此該對象無法回收,如果程序一直運行,則該對象永遠(yuǎn)無法回收,因為有個強引用永遠(yuǎn)指向他,造成了內(nèi)存泄露問題
因此Entry為弱引用,ThreadLocalMap的key弱指向threadLocal對象t1,只要有GC,這個t1就會被回收

內(nèi)存泄露場景2:
當(dāng)我們通過弱引用將ThreadLocal對象t1回收以后,就出現(xiàn)了key為null,但是value存在的情況,value則面臨無法回收的局面,因為已經(jīng)無法通過這個null找到這個value,導(dǎo)致越來越多的這種積累,造成內(nèi)存泄漏
**總結(jié):
因此正常的threadLocal使用方法是確定new出來的Person不再引用以后,使用t1.remove()將整個Entry行從ThreadLocalMap中刪除**