前言
- 引用的作用就是在GC的时候用作判断,一般采用的根可达算法(引用计数法会有循环引用的问题,所以我们不用)
- 内存泄漏:我们不再使用一个对象,但其由于仍然有引用指向它,导致一直无法被垃圾回收器回收,这个称为内存泄漏
强引用
普通常见的引用
示例
public class NormalReference {
public static void main(String[] args) throws IOException {
M = new M();
m = null;
System.gc();//DisableExplicitGC
System.in.read();
}
}
软引用
示例
软引用在内存够用时不会被回收,在内存不够发生GC时会被回收
// 前提:JVM启动时-Xmx设置为20M
public class SoftReference {
public static void main(String[] args) throws IOException {
// m在栈中,堆中有一个SoftReference对象,此引用为强引用;
// SoftReference对象又有一个软引用指向一个byte数组,此引用为软引用
SoftReference<Byte[]> m = new SoftReference<>(new byte[1024*1024*10]); //10M
// 如果需要得到软引用的内容,使用软引用的get()方法即可
System.out.println(m.get())//有输出,数组对象值
System.gc();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(m.get());//有输出,数组对象值
// 再分配一个数组,heap将装不下,此时会将软引用的对象给回收
byte[] b = new byte[1024*1024*15];
System.out.println(m.get());//null
}
}
用途
缓存,例如常用大图片
弱引用
弱引用的对象,只要出现了GC就会被回收,适合ThreadLocal这种对象,因为不仅本身有强引用,在线程内部又会有key的弱引用,采用此引用可以保证强引用消失时能够被GC掉而不造成内存泄漏
示例
// 弱引用:被问到最多的一个
public class WeakReference {
public static void main(String[] args) throws IOException {
WeakReference<M> m = new WeakReference<>(new M());
System.out.println(m.get());// 有输出
System.gc();
System.out.println(m.get());// null,一旦GC就直接回收了
ThreadLocal<M> tl = new ThreadLocal<>();
tl.set(new M());
tl.remove();
}
}
// ThreadLocal源码
public void set(T value) {
Thread t = Thread.currentThread();
// 根据当前线程对象获得一个map,然后往当前线程的map(threadLocals)里面放key value,注意key是ThreadLocal对象
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
ThreadLocalMap getMap(Thread t) {
// 可以看到这个map实际上是线程自己的一个map
return t.threadLocals;
}
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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;
}
}
// 可以看到key value最终是一个Entry的形式放进threadLocals里的
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
// Entry本身继承自弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
用途
- ThreadLocal:当使用ThreadLocal对象进行set(value)时,它会先去拿到
当前线程的ThreadLocalMap threadLocals
,然后往该map里面放key(ThreadLocal
对象)和value,这样每次get出来的内容,一定是从当前线程独享的map里获取的信息。- 当往当前线程自身map里面放入以ThreadLocal对象为key的entry时,产生了两个引用:外部建立ThreadLocal对象时的强引用,当前线程内部threadLocals这个map的key的弱引用
- 当外部强引用消失时,内部key的弱引用,就能保证这个ThreadLocal对象能够在GC时被回收掉
内存泄漏点一(弱引用帮我们解决了这个问题):ThreadLocal对象
- 当外部ThreadLocal对象的强引用消失时,线程内部的threadLocals map里该ThreadLocal对象key对应的这一行也就再也无法获得(正常是通过tl.get()方法获得),此时需要我们自己手动去执行tl.remove()将这一行的value给删除
内存泄漏点二:当前线程threadLocals map中ThreadLocal对象作为key对应的那个value
- 当外部强引用消失时,内部key的弱引用,就能保证这个ThreadLocal对象能够在GC时被回收掉
- 当往当前线程自身map里面放入以ThreadLocal对象为key的entry时,产生了两个引用:外部建立ThreadLocal对象时的强引用,当前线程内部threadLocals这个map的key的弱引用
虚引用
示例
// 虚引用:永远get不到
// 虚引用:永远get不到
public class PhantomReference {
private static final List<Object> LIST = new LinkedList<>();
// 首先要有一个队列
private static final ReferenceQueue<> QUEUE = new ReferenceQueue<>();
public static void main(String[] args) throws IOException {
// 虚引用pr,指向PhantomReference对象
// PhantomReference对象,指向M对象,并且指明该虚引用所属队列是哪一个(QUEUE)
PhantomReference<M> pr = new PhantomReference<>(new M(), QUEUE);
// 此线程不断往加内存,使其膨胀,让垃圾回收器开始工作。(每次垃圾回收器回收一次,虚引用对象就会被回收)
new Thread(() -> {
while(true) {
LIST.add(new byte(1024*1024));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
System.out.println(pr.get());// null
}
}).start();
// 监控线程,监控QUEUE是否有内容,拿出来即表明堆外内存已经回收
new Thread(() -> {
while(true) {
Reference<? extends M> poll = QUEUE.poll();
if(poll != null) {
System.out.println("--- 虚引用对象被jvm回收了 ---" + poll);
}
}
}).start();
}
}
用途
- 管理堆外内存,Netty中的零拷贝
- 一、从网络上访问一个数据的步骤:
- 网卡读到数据,交给操作系统
- 操作系统将数据积攒成一个buffer
- 如果JVM需要,将会复制到JVM管理的内存区
- 二、如果JVM想要把一块数据从网络上写出去
* JVM需要交给操作系统,再写给网卡 - 以上两种情况,其中JVM与系统之间的复制,可以省略->堆外内存管理(直接内存管理)。
- 在NIO中可以通过Java对象DirectBuffer指向堆外内存
- 在垃圾回收器中有一个垃圾线程,专门监听DirectByteBuffer对象,如果没了就要去把堆外的内存去回收。如何做到?给这个对象加一个虚引用,则能够在该对象被回收时,其信息会被添加到队列QUEUE里面,上面的监听线程就是监听QUEUE的
待自学点
AtomicReference
网友评论