其实本来打算先聊threeadlocal的,后来发现其的使用了一种引用–弱引用,所以这个就需要聊一下引用的四种方式:强引用,软引用,虚引用,弱引用。
前提
在聊四种引用之前, 我们先补充一下
补充两个概念: 内存溢出和内存泄露。
- 内存溢出 (out of memory): 这个是指,再程序运行申请内存时,没有组有个的内存空间可以使用了,所以会出现这个错误。简单的说就是:宾馆已满,入住业务暂停。
- 内存泄露( memory leak): 程序某些对象使用内存后,却无法释放使用的内存,这个叫做内存泄露。而这个也会导致out of memory(溢出)的错误,毕竟占的空间多了也会影响其它程序申请内存不够。简单的说就是占着茅坑不拉屎。
ide 配置虚拟机运行内存大小
一般的时候不需要修改这个配置,现在电脑的运行内存都是16G以上了,除非你用电脑启动了很多项目,然后多个运行程序占用过多的内存。有可能会崩溃的,或者报各种错误。而此处说这个的原因是为了演示几种引用的特点,所以需要调整运行内存大小。
具体位置在:
然后会打开一个配置文件:
这个是默认的配置信息,不过调整运行内存的参数有两个:
- xms就是start启动内存占用,默认是128M.
- xmm就是max最大内存占用, 默认是2048M.
如果觉得不够用的话,自己进行简单的修改即可。这个配置的是全部的运行内存所占大小。
强引用
现在看一下引用类:
可以看出引用抽象类下面只有三个引用类,而不是我前面所言的是四个为什么呢?而且还没有要说的一个引用强引用。
其实强引用再java.lang.ref中并没有实际对应的类型,不过显示使用中却是最常用的一种,比如:
// 自己创建的类,然后通过new
class Test{}
Test test=new Test();
// 或者调用java内部类
String string=new String();
所以说一般把一个对象赋值给一个引用变量,这个引用变量就是强引用。而只要变量名和对象存储位置一直存在,那么这个对象存储的位置就一直存在,不会被垃圾回收器回收,所以强引用是造成java内存泄露的主要原因之一。
对于一个普通对象,如果没有其它引用关系,只要超过引用的作用与或者之间将强引用赋值为null(也就是变量名=null),一般默认就是可以被垃圾回收的。
通过这个可以看出强引用特点:
- 强引用可以之之间访问目标对象。
- 强引用(存在时)指向的镀锡任何时候都不会被回收,JVM宁可抛出内存溢出的错误也不会回收。
- 强引用可能会导致内存泄露的问题,防止这个问题的,一般不使用的话,直接将其赋值为通过赋值full来切断这个引用。
演示一下:
首先说两个方法:
方法 | 描述 |
finalize() | Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。所有的对象都有这个方法,其通Object来继承。 |
System.gc() | 用于垃圾收集器,调用垃圾收集器将回收未使用的对象,简单说就是回收没有被任何可达变量指向的对象 |
首先如下测试:
public class test {
public static void main(String[] args) throws Exception {
System.out.println("测试开始");
testobject tobject = new testobject();
System.gc();
// 回收需要时间,所以人工暂停一下
Thread.sleep(200);
System.out.println("测试结束");
}
}
class testobject {
// 为了演示而重新了这个方法,但是开发的时候千万不要,不然项目经理会打死你的,因为会影响垃圾回收
@Override
protected void finalize() throws Throwable {
System.out.println("finalize");
}
}
然后如下测试:
public class test {
public static void main(String[] args) throws Exception {
System.out.println("测试开始");
testobject tobject = new testobject();
tobject=null;
System.gc();
// 回收需要时间,所以人工暂停一下
Thread.sleep(200);
System.out.println("测试结束");
}
}
class testobject {
// 为了演示而重新了这个方法,但是开发的时候千万不要,不然项目经理会打死你的,因为会影响垃圾回收
@Override
protected void finalize() throws Throwable {
System.out.println("finalize");
}
}
软引用
先看一下官网解释:
软引用对应的类为 java.lang.ref.SoftReference, 一个软引用中的对象,不会很快被JVM回收,JVM会根据当前堆的使用情况来判断何时回收,当堆的使用率超过阈值时,才回去回收软引用中的对象。
这个先来要给例子在解释:
SoftReference<byte[]> sf=new SoftReference<byte[]>(new byte[1024*1024*20]);
具体用图演示如下:
本来打算演示一下呢,因为记得IDE是可以调整运行内存的,我调试了一下,IDE直接崩了,具体忘了如何调整某一个程序的运行内存,是不是可以调整安卓模式或者小程序模式才行呢这个以后在研究吧,现在只能通过理论模拟了。
如果是强引用:
//比如设置了最大的运行空间为30M
// 设置的是20M
byte[] b1=new byte[1024*1024*20];
// 然后在创建一个
byte[] b2=new byte[1024*1024*20];// 这里会报错的,内存溢出,毕竟20+20>30 ,如果在设置b2之前,赋值b1=null,就不会报错了
如果使用软引用:
// 不要使用b1=new byte[1024*1024*20] 然后再放入软应用构建方法的参数 这样也多了一个强引用
SoftReference<byte[]> sf=new SoftReference<byte[]>(new byte[1024*1024*20]);
// 如何得到这个数组的值呢:
sf.get();// 得到数组对象
byte[] b2=new byte[1024*1024*20];
sf.get();// null
因为看图可以知道sf指向是一个强引用还占用内存,但是其软引用执行的数组发现新来的数组相加超过运行内存了,先垃圾回收,发现不过把软引用使用的空间回收,所以足够存储b2也就不会报错.
其本身有一个方法: **get() : 得到软引用指向的对象 **
构造方法:
SoftReference(T referent)
创建引用给定对象的新的软参考。
SoftReference(T referent, ReferenceQueue<? super T> q)
创建引用给定对象并在给定队列中注册的新软引用。这个在虚引用中使用,因为其不行需要引用队列
引用场景:
- 对一些非必要的大型文件呈现的程序,可以使用这个软引用,毕竟要保证核心的程序可以运营,如果核心业务需要内存不够就可以将其软引用占领的资源释放后使用.
- 浏览器后退的这样的业务也可以使用软应用,保证正常系统或者浏览器使用的同时才考虑可以实现后退这个功能,不然访问很多页面,这个后退功能会占领太多的内存了.
弱引用
弱引用需要用java.lang.ref.WeakReference实现,它比软引用的生存期更短,对于弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否够,都会回收该对象的占用内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
先体验:
public class test {
public static void main(String[] args) throws Exception {
WeakReference wk=new WeakReference<testobject>(new testobject());
System.out.println(wk.get());
System.gc();
}
}
class testobject {
// 为了演示而重新了这个方法,但是开发的时候前往不要,不然项目经理会打死你的,因为会影响垃圾回收
@Override
protected void finalize() throws Throwable {
System.out.println("finalize");
}
}
其关系看图:
会发现无论内存够不够原价弱引用就会被垃圾回收干掉.
说实话这四个引用中最重要的就是弱引用,因为这个涉及到多线程中使用的一个对象类ThreadLocal(一个为线程存储独立数据的类).但是这个时候又有了一个问题:那就是弱引用其随时可能会被干掉,那么如何保证数据呢?
不过这个现在只是记住其特点吧, 再底层代码中其使用很多的,比如spring等
构造方法:
WeakReference(T referent)
创建引用给定对象的新的软参考。
WeakReference(T referent, ReferenceQueue<? super T> q)
创建引用给定对象并在给定队列中注册的新软引用。这个在虚引用中使用,因为其不行需要引用队列
虚引用
虚引用要通过java.lang.ref.PhantomReference类来实现,不过看起名字就知道,虚引用就是假的引用说白就就是没有引用.在任何时候都会被回收,他不能决定对象的生命周期,它不能单独使用也不能访问对象,虚引用必须和引用队列联合使用。
所以创建虚引用的时候需要与引用队列(ReferenceQueue)一起使用.
所以其构造方法只有一个:
PhantomReference(T referent, ReferenceQueue<? super T> q)
创建引用给定对象并在给定队列中注册的新软引用。这个在虚引用中使用,因为其不行需要引用队列
所以虚引用的get方法总是返回null,引起无法访问对应的引用对象,所以虚引用主要用来跟踪对象的回收,清理被销毁对象的相关资源.
public class test {
public static void main(String[] args) throws Exception {
Object a = new Object();
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> pr = new PhantomReference<>(a, queue);
System.out.println(a);//java.lang.Object@4554617c
System.out.println(pr.get());//java.lang.Object@4554617c
System.out.println(queue.poll());//null
System.out.println("-------------------");
a = null;
System.gc();
System.out.println(a);//null
System.out.println(pr.get());//null //虚引用在回收之前被加入到了引用队列中
System.out.println(queue.poll());//java.lang.ref.PhantomReference@6e0be858
}
}
可以看出当虚引用的对象消失的时候,ReferenceQueue会接受到被释放的对象是PhantomReference对象本身.
总结:
引用 | 特点 |
强引用 | 可以直接访问的对象,(在使用域中)不会被回收,可能会造成内存泄漏. |
软引用 | 内存充足就留着,但是内存不够或者达到阈值就会强制收回 |
弱引用 | 生命周期很短,无论内存释放充足,都会被强制回收,不过GC线程优先级很低,不一定会及时发现 |
虚引用 | 不影响生命周期,任何时候都会被收回,用来跟踪回收空间,清理相关资源. |