一、java中四种引用类型
- 强引用 NormalReference(一个普通变量指向一个对象,引用消失以后,对象就会被GC)
Object o = new Object() - 软引用 SoftReference(有一个软引用对象,软引用对象中有个引用指向一个对象,这个对象是被软引用连着的,在GC的时候会被特殊处理,堆内存不够用的时候就会被回收)
/**
* -Xmx30M 设置最大堆内存为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());
// 再分配一个数组,占据堆内存空间的超过65%=10M,就会为null,小于65%比如9M则为对象地址
// 如果直接和上面的对象占用堆内存大小加起来大于30M则java.lang.OutOfMemoryError: Java heap space
byte[] b = new byte[1024*1024*10];
System.out.println(m.get());
}
}
- 弱引用 WeakReference(有一个弱引用对象,弱引用对象中有个引用指向一个对象,这个对象只要遇到GC就会被回收)弱引用作用是解决内存泄露
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(有跟没有差不多,永远get不到,作用是管理堆外内存)
二、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";
}
上述打印结果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";
}
打印结果为null
源码解析
java.lang.ThreadLocal #set
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的本地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的结构是一个entry数组,entry是链表节点,下面也是new一个entry键值对作为threadLocalMap的结构数组中成员
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) {
// 调用子类的构造方法的时候,会先去实例化父类
// 下面这句等效于 new WeakReference(new K()),即k对象是被弱引用指向的
super(k);
value = v;
}
}
}
java.lang.ThreadLocal.ThreadLocalMap #getMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
获取的是当前线程里面的threadlocalmap,自然B线程无法获取A线程放置的内容,threadLocal最明显的使用时spring中的事务@Transaction
流程:new 一个threadLocal,然后调用set方法,引用了当前线程的threadLocalMap,然后创建一个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();
内存泄露场景1:
假设Entry为强引用,因为是强引用,当我们写t1=null的时候(或者main方法退出),t1不再使用的时候,这个new出来的threadLocal应该被回收掉,可是因为在t1中set了一个new Person(),则ThreadLocalMap中仍然有个entry的key指向这个ThreadLocal对象t1,因此该对象无法回收,如果程序一直运行,则该对象永远无法回收,因为有个强引用永远指向他,造成了内存泄露问题
因此Entry为弱引用,ThreadLocalMap的key弱指向threadLocal对象t1,只要有GC,这个t1就会被回收
内存泄露场景2:
当我们通过弱引用将ThreadLocal对象t1回收以后,就出现了key为null,但是value存在的情况,value则面临无法回收的局面,因为已经无法通过这个null找到这个value,导致越来越多的这种积累,造成内存泄漏
**总结:
因此正常的threadLocal使用方法是确定new出来的Person不再引用以后,使用t1.remove()将整个Entry行从ThreadLocalMap中删除**