分为强引用、软引用、弱引用、虚引用
最常用的普通引用,只有没有任何一个变量指向new出的M对象时,它才会被GC回收。
M m = new M();
当对象被回收时,会调用finalize()方法。这个方法一般不要写
SortReference m = new SortReference<>(new byte[1024*1024*10]);
System.out.println(m.get());//不为空
System.gc();
try {Thread.sleep(500);
}catch(InterruptedException e){e.printStackTrace();
}
System.out.println(m.get());//不为空//再分配一个数组,heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用的对象回收
byte[] b = new byte[1024*1024*15];
System.out.println(m.get());//为空
栈中变量m指向SoftReference类型的实例对象,该对象指向一个10M的字节数组——这个引用是软引用。(下图中右侧波浪线部分)
拿到软引用的内容,调用get方法即可。
这个程序是带参数的 -Xmx20M,JVM分配的空间为20M,一开始调用GC,软引用也不会回收。但是后来又创建了15M的数组,堆内存分配不下,此时软引用被回收。
软引用主要用在缓存上面。
涉及到ThreadLocal的典型应用。
public static void main(String[] args){WeakReference m = new WeakReference<>(new M());System.out.println(m.get());//不为空System.gc();System.out.println(m.get());//为空,并调用finalize方法ThreadLocal tl = new ThreadLocal();tl.set(new M());tl.remove();
}
经过垃圾回收,弱引用就直接被回收了。
每个线程自己独立拥有,线程存在,对象就一直存在。
在第二个线程中向tl中set了一个Person对象。在第一个线程中,两秒以后区读它。结果输出空值。
public class ThreadLocalTest{static ThreadLocal tl = new ThreadLocal<>();public static void main(String[] args){new Thread(()->{try{TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e){e.printStackTrace(); }System.out.println(tl.get());//空值 }).start(); new Thread(()->{try{TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e){e.printStackTrace(); }tl.set(new Person()); }).start();}static class Person{String name = "zhangsan"; }
}
ThreadLocal往里面set任何值,一定是和当前线程有关系,和其他线程没有关系。
Spring中有个注解@Transactional ,假如将m()方法标记为事务。
@Transactional
m(){m1();m2();
}
假设m1 m2都会访问数据库,由于它们位于同一个transaction中,所以必须保证拿到的数据库连接是同一个,就是用ThreadLocal实现的。因为ThreadLocal中的connection是和当前线程有关系的。从当前线程拿,拿到的永远都是同一个。
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);//ThreadLocalMap 的set方法elsecreateMap(t, value);
}
set方法是将传入的对象,set到了一个map里面,当前的threadLocal这个对象作为key,传入的参数作为value。
getMap方法:
ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}
t是当前线程,返回的是当前线程的threadLocals变量
ThreadLocal.ThreadLocalMap threadLocals = null;
static class ThreadLocalMap {static class Entry extends WeakReference> {Object value;Entry(ThreadLocal> k, Object v) {super(k);value = v;}}private static final int INITIAL_CAPACITY = 16;/*** The table, resized as necessary.* table.length MUST always be a power of two.*/private Entry[] table;/*** The number of entries in the table.*/private int size = 0;....../*** Construct a new map initially containing (firstKey, firstValue).* ThreadLocalMaps are constructed lazily, so we only create* one when we have at least one entry to put in it.*/ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);}
Thread类中有一个成员变量threadLocals,里面装的是和当前线程相关的threadLocal。threadLocals是ThreadLocal的内部静态类ThreadLocalMap,里面用了一个Entry数组保存键值对。
key是threadLocal对象,在线程中new几个ThreadLocal就有几个key。
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);//new了一个Entry对象int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();
}
参数是一个key value对,set方法是new了一个Entry对象,然后把这个Entry对象放到了ThreadLocalMap里(实际是一个Entry数组)。
Entry是ThreadLocalMap的一个静态子类
static class Entry extends WeakReference> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal> k, Object v) {super(k);value = v;}
}
Entry继承了WeakReference类,说明Entry是一个弱引用。
当创建Entry对象时,调用了super(k), new 了一个WeakReference对象,这个WeakReference的弱引用指向key。
这里为什么要用弱引用指向ThreadLocal对象?(上图虚线部分)
防止内存泄漏。ThreadLocal用一个弱引用解决了垃圾回收问题。
当tl被回收,ThreadLocal对象(ThreadLocalMap对应记录的key)会自动回收(因为弱引用),但map中的记录不会自动被删掉。所以一旦ThreadLocal对象不用了,必须调用remove方法(下图20行),不然还是会产生内存泄漏(value的值不再使用但如果不回收还是会保存)。
虚引用比弱引用还弱,永远get不到
public class T04_PhantomReference{private static final List
虚引用首先有一个队列,创建虚引用时需要同时指定这个虚引用的队列是哪一个。
java api : NIO new IO,从网络上访问数据,网卡写入操作系统的buffer中,如果JVM需要,再复制到JVM管理的内存中。JVM要写数据,先写入到os的buffer中,再复制到网卡。中间多了一步没必要的操作,就是从JVM管理内存向操作系统内存这块复制,这个复制过程其实是可以省略的。
在NIO中就提供了一种直接内存管理,或者叫堆外内存管理。JVM平时管理的内存是一个堆,但是如果启用NIO之后,向网络上写数据时,是可以直接管理堆外的操作系统的那块内存。不需要在JVM里面写一遍数据再拷贝一遍到OS内存了,这叫zero copy,它的效率会高很多。
这里会有java的对象代表这块内存,DirectByteBuffer,叫直接内存或堆外内存。这块内存不在JVM内存范围,所以GC无法回收。所以,在垃圾回收器中,有一个垃圾线程,专门监听有哪些堆外内存的DirectByteBuffer对象。当它被回收时,指向的堆外内存必须随之删掉(否则会发生内存泄漏)。
什么时候能知道DirectByteBuffer对象没有了呢?在它上面加一个虚引用,这个虚引用只有一个作用,当这个对象被回收时,它的某一个信息会被加到队列Queue里。所以GC只要检测什么时候Queue有新内容,说明某一个直接内存管理的对象被回收了,同时就把对应的堆外内存进行处理。
所以,虚引用只有一个作用,管理堆外内存。谁来管理堆外内存:JVM虚拟机专门的GC线程。
DirectByteBuffer分配的内存空间是在操作系统的外面,是操作系统的内存。
netty里面有一个zero copy的技术,就是用它来实现的。