引用

强、弱引用

强引用就是我们经常使用的引用,其写法如下

1
StringBuffer buffer = new StringBuffer();

这句话的意思是:

  1. 通过new StringBuffer创建了一个对象并在内存中开辟了一段存储空间
  2. 创建一个StringBuffer创建一个变量buffer并且指向new StringBuffer
  3. 我们可以通过buffer来使用new 的 StringBuffer

这时我们称buffer是new StringBuiffer的强引用,强引用是不会被jvm回收的

强引用是造成内存泄露的主要原因之一,为什么这么说呢?因为有强引用指向new StringBuffer所以Java认为他还在被使用,所以不会给我回收掉。经常使用idea做开发的同学可能会注意到,idea有时会给我们一个警告在变量上,说 xxx never be used,某某变量从未被使用过。这时候并不会被回收掉,但是会造成内存浪费。当引用被置空时,GC运行时才会被回收掉,因此想要被回收需要满足什么条件呢

  1. 没有任何引用指向它

    1
    2
    Object c = new Car();
    c=null;
  2. GC运行时

但是手动置空是非常繁琐且不符合常理的,对于简单的情况,这些是不需要程序员来做的,因为在Java中,对于简单对象,当调用他的方法执行完毕后,指向它的引用就会被从stack中popup,所以在下一次GC运行时就可以把它回收掉。可以使用java -verbose:gc来观察gc的行为。怎么知道调用他的方法执行完了嘛,有一个计数器。

但是也有特殊情况,当使用cache的时候,由于cache是程序行所需要的,那么程序只要在运行cache就不应该被GC回收掉,那么随着程序的运行这样的object会越来越多,这些Object的回收就要靠程序员自己来维护了。所以这时候引入了弱引用 weak reference相对于前面举例中的strong reference:

weak reference

1
Object c = new Car(); //只要c还指向car object, car object就不会被回收

当一个对象只被weak reference指向时而没有任何Strong reference 如果GC运行,那么这个对象就会被回收掉。

1
WeakRefernce<Car> weakCar = new WeakReference<Car>(car);

当需要获得weak reference 引用的Object时,首先需要判断是否被回收掉

1
weakCar.get();

如果为空说明weakCar指向的对象已经被回收掉了。

看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package weakreference; 
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;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package weakreference;

import java.lang.ref.WeakReference; /** * @author wison */
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已经被回收了,值得注意的即使有car指向对象,且是强引用,weak reference指向的对象还是被回收了,这是因为进入while循环后,Java发现car再没有被使用过了,所以进行了优化(置空?)将代码修改一下;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package weakreference;

import java.lang.ref.WeakReference; /** * @author wison */
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){
System.out.println("here is the strong reference 'car' "+car);
if(weakCar.get()!=null){
i++;
System.out.println("Object is alive for "+i+" loops - "+weakCar);
}else{
System.out.println("Object has been collected.");
break;
}
}
}
}

这时候weak reference指向的就不会被回收,因为他的一个strong reference被使用了。

weak reference的一个特点就是它何时被回收是不确定的,因为这是由于GC的不确定性决定的。所以一般用weak reference引用的对象是有价值被cache的,而且很容易被重新构建,且很消耗内存的对象

reference queen

在weak reference指向的对象被回收后,weak reference本身也没什么用了,Java提供了一个reference queen来保存那些所指向的对象已经被回收了的reference,用法是在定义WeakReference的时候将一个ReferenceQueen的对象作为参数传入构造函数

softreference

soft reference和weak reference一样,但是在被GC回收的时候多了一个条件,当内存不足时,soft reference指向的object才会被回收,soft reference比weak reference更适合做cache object的reference,因为它尽可能的retain cached objects减少重建他们的消耗

Autowried Map

spring boot的自动注入大家知道吧

1
2
@Autowried
private Map<String, UserService> map; // UserService用@Component注解标注

这种注入方式不知道大家有没有用过,通过这种方式会把所有实例化的UserService注入到这个Map中,然后key为这个这个Bean的id或者name。

那么问题来了,现在这些实例化的Bean被存储到了map里面,他的key是这个Bean的name,如果这个Bean被销毁了,也就是不需要了,但是这时候这个map里面仍然存着他的相关数据,这样可能就会造成内存泄漏。

ThreadLocal

我们知道每个线程都有自己的私有变量,线程在操作的时候他不能直接操作元数据,他是拷贝一份到自己这里来,然后再来操作的。ThreadLocal就是用来存放这个线程的数据的,ThreadLocal在项目中的应用通常用来存储当前登陆者的个人信息,在数据库连接中用来存放session。

怎么存储的呢?

ThreadLocalMap

ThreadLocal内部有一个静态内部类ThreadLocalMap,里面定义了Entry来保存数据,而且继承的是弱引用。在Entry的使用ThreadLocal来作为Map的key。

那么问题来了,线程是有他的生命周期的,在它用完后他被销毁了也就是没有这个线程了,那么他的id啥的都没了,ThreadLocalMap又是使用线程作为key的,那现在key都没了,我们就没办法来拿到这个map里面的value了,这也就造成了内存的泄漏。

那么ThreadLocal是怎么解决这个问题的呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}

注意这一行注释

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.

下面研究一下WeakReferences这个玩意

WeakReferences

WeakReferences如字面意思,弱引用。当一个对象仅仅被weak references指向时,没有任何strong references指向的时候,这是会被GC回收掉不管当前的内存空间是否足够。

1
2
3
4
5
6
7
8
9
10
public class WeakReference<T> extends Reference<T> {
public WeakReference(T referent) {
super(referent);
}

public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}

}
  • WeakReference(T referent):referent就是被弱引用的对象(注意区分弱引用对象和被弱引用的对应,弱引用对象是指WeakReference的实例或者其子类的实例),比如有一个Apple实例apple,可以如下使用,并且通过get()方法来获取apple引用。也可以再创建一个继承WeakReference的类来对Apple进行弱引用,下面就会使用这种方式。

    1
    2
    WeakReference<Apple> appleWeakReference = new WeakReference<>(apple);
    Apple apple2 = appleWeakReference.get();
  • WeakReference(T referent, ReferenceQueue<? super T> q):与上面的构造方法比较,多了个ReferenceQueue,在对象被回收后,会把弱引用对象,也就是WeakReference对象或者其子类的对象,放入队列ReferenceQueue中,注意不是被弱引用的对象,被弱引用的对象已经被回收了.

ThreadLocal 和 ThreadLocalMap 是什么?

  1. ThreadLocalMap是用来存储Thread的数据的
  2. ThreadLocalMap是ThreadLocal的一个静态内部类
  3. ThreadLocal是操作ThreadLocalMap的一个工具,通过ThreadLocal可以将一些对象保存到线程上实现同一线程不同方法之间共享

Thread、ThreadLocal 与 ThreadLocalMap 之间的关系

ThreadLocal可以有多个,他只是一个供我们操作ThreadLocalMap的工具,ThreadLocalMap是一个线程只有一个的

image-20221002204130186

ThreadLocal导致的内存泄漏的原因是什么?

程序员使用完ThreadLocalMap中的数据后没有清除掉这些数据,v是强引用如果不释放就会一直存在

ThreadLocalMap是维护在线程内部的,意味着如果线程不退出,那ThreadLocalMap保存的对象引用就会一直存在,由于GC是根据可达性分析的,存在强引用的对象是不会被回收的。而ThreadLocalMap中存储的都是强引用。如果垃圾一直不能被回收可能就会出现OOM问题。

如何清理 ThreadLocalMap 存储的对象

使用完ThreadLocalMap存储的对象后,只需要调用一下ThreadLocal的remove方法,就会将ThreadLocalMap中的K-V对的引用置空,垃圾收集器就会在合适的时机将k-v引用的对象所占的空间清空

为什么ThreadLocalMap使用弱引用key

ThreadLocalMap是与线程绑定的,线程不退出,强引用的key就不会被回收,当用户不能妥善处理好K-V时就会造成内存泄漏。使用弱引用,如果除了弱引用外没有强引用或者强引用没有被使用过就会在合适的时机被GC回收掉绝大多数的key(除static和全局key外),以减少内存泄漏

实际上最需要回收的是value对象,弱引用key只是一种挽救措施

ThreadLocalMap 为什么使用强引用 value,而不是弱引用

与key不同的是,key是作为索引使用的,实际用户需要的内容还是value,value需要在线程内共享

当局部value对象所在的方法结束时,栈桢被清空时,局部的value对象引用会被销毁,GC会清除没有引用的对象。如果此时设置成弱引用装入map,value会在某次GC中消亡,这明显不是我们想要看到的,我们想要的是可以在线程内部共享。,只有强引用能达到这个目的。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!