文章目录
- 一、Java对象的四种引用
- 1.引用分类
- 2.强引用
- 3.软引用
- 4.弱引用
- 5.ThreadLocal——线程本地变量
- (1)ThreadLocal属于线程独有示例
- (2)ThreadLocal的使用实例
- (3)ThreadLocal的set方法源码
- (4)ThreadLocalMap 的set方法
- (5)Entry的源码
- 6.虚引用
- (1)虚引用特点
- (2)NIO
- (3)虚引用作用:管理堆外内存
一、Java对象的四种引用
1.引用分类
分为强引用、软引用、弱引用、虚引用
2.强引用
最常用的普通引用,只有没有任何一个变量指向new出的M对象时,它才会被GC回收。
M m = new M();
当对象被回收时,会调用finalize()方法。这个方法一般不要写
3.软引用
SortReference<byte[]> 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的数组,堆内存分配不下,此时软引用被回收。
软引用主要用在缓存上面。
4.弱引用
涉及到ThreadLocal的典型应用。
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());//为空,并调用finalize方法
ThreadLocal<M> tl = new ThreadLocal();
tl.set(new M());
tl.remove();
}
经过垃圾回收,弱引用就直接被回收了。
5.ThreadLocal——线程本地变量
每个线程自己独立拥有,线程存在,对象就一直存在。
(1)ThreadLocal属于线程独有示例
在第二个线程中向tl中set了一个Person对象。在第一个线程中,两秒以后区读它。结果输出空值。
public class ThreadLocalTest{
static ThreadLocal<Person> 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任何值,一定是和当前线程有关系,和其他线程没有关系。
(2)ThreadLocal的使用实例
Spring中有个注解@Transactional ,假如将m()方法标记为事务。
@Transactional
m(){
m1();
m2();
}
假设m1 m2都会访问数据库,由于它们位于同一个transaction中,所以必须保证拿到的数据库连接是同一个,就是用ThreadLocal实现的。因为ThreadLocal中的connection是和当前线程有关系的。从当前线程拿,拿到的永远都是同一个。
(3)ThreadLocal的set方法源码
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);//ThreadLocalMap 的set方法
else
createMap(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<ThreadLocal<?>> {
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。
(4)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);//new了一个Entry对象
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
参数是一个key value对,set方法是new了一个Entry对象,然后把这个Entry对象放到了ThreadLocalMap里(实际是一个Entry数组)。
(5)Entry的源码
Entry是ThreadLocalMap的一个静态子类
static class Entry extends WeakReference<ThreadLocal<?>> {
/** 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的值不再使用但如果不回收还是会保存)。
6.虚引用
(1)虚引用特点
虚引用比弱引用还弱,永远get不到
public class T04_PhantomReference{
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);
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(phantomReference.get());//为空
}
}).start();
//此线程可以当作监控堆外内存的垃圾回收,一直从QUEUE中取元素,如果取到,说明有虚引用对象被回收了。
new Thread(()->{
while(true){
Reference<? extends M> poll = QUEUE.poll();
if(poll != null){
System.out.println("---虚引用对象被jvm回收了---" + poll);
}
}
}).start();
}
}
虚引用首先有一个队列,创建虚引用时需要同时指定这个虚引用的队列是哪一个。
(2)NIO
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线程。
(3)虚引用作用:管理堆外内存
DirectByteBuffer分配的内存空间是在操作系统的外面,是操作系统的内存。
netty里面有一个zero copy的技术,就是用它来实现的。