Strong Reference
- 我们日常绝大多数引用均是强引用
- 如果对象拥有强引用,就表示它是可达的,那么垃圾收集器就不会将其回收
- 如果将某个强引用显式置为null,就表示该引用不再指向对象,若该对象没有其他引用指向他,那么会在适当时机就会被垃圾回收器回收。
举例:
我创建了一个对象listA,并向listA中添加了100个元素,大家知道ArrayList的底层是数组,在创建listA之后,调用add方法之前,listA中存储数据的数组(Object[] elementData)默认是为空的elementData=null,在调用add方法之后才会初始化这个数组,并且默认长度为10(元素个数超过数组长度之后会重新创建一个数组,并将原数据复制到新数组,数组扩展系数为原来的1.5倍)。那么在调用add方法添加元素之后listA内存占用有如下两个:
1、系统为listA中数组elementData分配的内存;
2、向listA中添加元素占用的内存;
在ArrayList中底层数组是Object类型,数组中存储的并不是数据本身,而是数据的引用,那么现在在看clear的方法
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
方法中将数组元素清空设置为null,即清除了对所有元素的引用,那么系统在gc的时候会将所有的元素清除,释放元素所占用的内存空间。但是此时并没有释放系统分配给elementData数组的空间。
个人认为这种方式没有任何问题,因为通常我们在调用clear方法清空数据之后,往往还会向listA中重新添加数据,如果在clear的时候将elementData也设置为null,那么在重新添加数据的时候如果数据量很大,那么又会涉及到因为扩展数组长度复制数据而浪费时间。所以如果listA只用一次,那么可以在调用clear之后把listA也设置为null来释放全部的内存。要看具体场合用具体的功能。
listA.clear();
listA = null;
对list使用的个人建议
1、如果能大概知道有多少个元素,那么在创建list的时候可以直接指定长度,避免因扩展长度复制数据而浪费时间,List listA = new ArrayList(100000);
2、如果集合中存储了大量的元素,并且集合已经使用完毕,那么尽量调用clear方法来及时的释放空间。如果集合不会重复使用,那么将集合也设置为null将内存一并释放。这样有助于gc能更快完成清除内存的工作。
其他三种类型的引用
软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)
软引用(SoftReference)
如果一个对象只具有软引用,那就类似于可有可物的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。
如:
public class Test {
public static void main(String[] args){
System.out.println("开始");
A a = new A();
SoftReference<A> sr = new SoftReference<A>(a);
a = null;
if(sr!=null){
a = sr.get();
}
else{
a = new A();
sr = new SoftReference<A>(a);
}
System.out.println("结束");
}
}
class A{
int[] a ;
public A(){
a = new int[100000000];
}
}
当内存足够大时可以把数组存入软引用,取数据时就可从内存里取数据,提高运行效率
软引用在实际中有重要的应用,例如浏览器的后退按钮。
按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。
(1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建
(2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出
这时候就可以使用软引用
弱引用(WeakReference)
如果一个对象只具有弱引用,那就类似于可有可物的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
如:
Object c = new Car(); //只要c还指向car object, car object就不会被回收
WeakReference<Car> weakCar = new WeakReference(Car)(car);
当要获得weak reference引用的object时, 首先需要判断它是否已经被回收:
weakCar.get();
如果此方法为空, 那么说明weakCar指向的对象已经被回收了.
下面来看一个例子:
public class Car {
private double price;
private String colour;
public Car(double price, String colour){
this.price = price;
this.colour = colour;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getColour() {
return colour;
}
public void setColour(String colour) {
this.colour = colour;
}
public String toString(){
return colour +"car costs $"+price;
}
}
public class TestWeakReference {
public static void main(String[] args) {
Car car = new Car(22000,"silver");
WeakReference<Car> weakCar = new WeakReference<Car>(car);
int i=0;
while(true){
if(weakCar.get()!=null){
i++;
System.out.println("Object is alive for "+i+" loops - "+weakCar);
}else{
System.out.println("Object has been collected.");
break;
}
}
}
}
在上例中, 程序运行一段时间后, 程序打印出”Object has been collected.” 说明, weak reference指向的对象的被回收了.
如果要想打出的是
Object is alive for “+i+” loops - “+weakCar
那么只要在这句话前面加上
System.out.println(“car==== “+car);
因为在此强引用了car对象
如果这个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象。
当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候你就是用弱引用。
这个引用不会在对象的垃圾回收判断中产生任何附加的影响。
虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
特别注意,在实际程序设计中一般很少使用弱引用与虚引用,使用软用的情况较多,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。
总结:
强引用:
String str = “abc”;
list.add(str);
软引用:
如果弱引用对象回收完之后,内存还是报警,继续回收软引用对象
弱引用:
如果虚引用对象回收完之后,内存还是报警,继续回收弱引用对象
虚引用:
虚拟机的内存不够使用,开始报警,这时候垃圾回收机制开始执行System.gc(); String s = “abc”;如果没有对象回收了, 就回收没虚引用的对象
ThreadLoacl的理解
通俗的讲:
ThreadLocal也叫做线程本地变量,ThreadLoacl为变量在每个线程中的都创建了副本,每个线程可以访问自己内部的副本变量,线程之间互不影响
从上图我们可以初步窥见ThreadLocal的核心机制:
1)每个Thread线程内部都有一个Map
2)Map里面储存线程本地对象key和线程的变量副本value
3)Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向Map获取和设置线程的变量值
这样对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,这样就形成了副本隔离,互不干扰
内部类的关系
内存泄露
ThreadLocal可能导致内存泄漏,为什么?
先看看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保存一个value时,会在ThreadLocalMap中的数组插入一个Entry对象,按理说key-value都应该以强引用保存在Entry对象中,但在ThreadLocalMap的实现中,key被保存到了WeakReference对象中。
这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
如何避免内存泄露
既然已经发现有内存泄露的隐患,自然有应对的策略,在调用ThreadLocal的get()、set()可能会清除ThreadLocalMap中key为null的Entry对象,这样对应的value就没有GC Roots可达了,下次GC的时候就可以被回收,当然如果调用remove方法,肯定会删除对应的Entry对象。
如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。
ThreadLocal<String> localName = new ThreadLocal();
try {
localName.set("占小狼");
// 其它业务逻辑
} finally {
localName.remove();
}