前言
Java语言中的数据类型可划分为值类型和引用类型。从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。
这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
值类型和引用类型
- 值类型:byte、short、int、long、float、double、char、boolean
- 引用类型:除了值类型,所有的类型都是引用类型。(数组、字符串、类、接口等)
一个具有值类型的数据存放在栈内的一个变量中:栈内分配内存空间,直接存储所包含的值,其值代表的数据本身。
而引用类型数据的变量值会存放在堆中,变量名(引用地址)会存放在栈中。
堆区: 1、存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令) 2、jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
栈区:1、每个线程包含一个栈区,栈中只保存基础数据类型(值就是本身)的对象和自定义对象的引用(不是对象),对象都存放在堆区中 2、每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问(所以也有人说栈是保存局部变量的)。 3、栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
方法区:又叫静态区。1、跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。 2、方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
在Java7之前,HotSpot虚拟机中将GC分代收集扩展到了方法区,使用永久代来实现了方法区。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。而在Java8中,已经彻底没有了永久代,将方法区直接放在一个与堆不相连的本地内存区域,这个区域被叫做元空间。
栈里的数据,函数执行完就不会存储了。这就是为什么局部变量每一次都是一样的。就算给他加一后,下次执行函数的时候还是原来的样子。(so,实例基本变量存在哪儿呢?所以:它显然不会是存在栈里的)
栈里存储的变量都是瞬时的(方法执行完就死了),而堆里的对象靠垃圾回收(年轻代、永久代(元空间))
Java引用类型
在Java中提供了四个级别的引用:强引用,软引用,弱引用和虚引用。
// @since 1.2 所在包是:java.lang.ref public abstract class Reference<T> { // 构造函数是非public的 所以只能通包内的类才能访问此构造函数 Reference(T referent) { this(referent, null); } Reference(T referent, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue; } ... public T get() { return this.referent; } public void clear() { this.referent = null; } public boolean isEnqueued() { return (this.queue == ReferenceQueue.ENQUEUED); } public boolean enqueue() { return this.queue.enqueue(this); } ... }
直接子类,只有下面这四个:
Reference<T>是JDK1.2提供的引用类型,它有四个子类分别代表着四种引用类型。其中只有强引用FinalReference类是包内可见,其余的都是public的。这也侧面表示了:强引用类型是Java默认的引用类型,直接使用即可
其实FinalReference根本就不是强引用,网上很多文章都以讹传讹,问下我有勘误。
强引用
强引用( Final Reference):只要强引用还存在,垃圾收集器永远不会回收(JVM宁愿抛出OOM异常也不回收强引用所指向的对)被引用的对象。但是,强引用可能会造成内存泄露哦
public class Main { public static void main(String[] args) throws Exception { Person person = new Person("fsx", 18); System.out.println(person); //强引用:直接使用 } }
这就是强引用的典型示例,我们平时绝大多数情况下都使用强引用即可。
强引用的特点:
- 可以直接访问目标对象。
- 所指向的对象在任何时候都不会被系统回收。JVM宁愿抛出OOM异常,也不会回收强引用所指向的对象。
- 可能导致内存泄漏。
若你的内存够大,无需考虑极致性能,所有引用都使用强引用也是ok的;强引用可直接访问,是Java默认的引用方式~
注意:我看到很多文章把FinalReference解释为强引用,这是错误的。
/** * Final references, used to implement finalization */ class FinalReference<T> extends Reference<T> { public FinalReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); } }
从它的javadoc也能看出来,这个类它和强引用没半毛钱关系。它倒是和Object的finalize()方法有关。关于它的详细介绍,可参阅笨神的大作:JVM源码分析之FinalReference完全解读
关于finalize方法的使用,亦可参阅这篇文章:java finalize方法总结、GC执行finalize的过程
软引用
软引用(Soft Reference):是用来描述一些还有用但并非必须的对象。对于软引用对象,如果内存
充足
(注意此处对充足二字的理解)gc不会管它,如果内存不够了,它就不能幸免了。
public class SoftReference<T> extends Reference<T> { static private long clock; private long timestamp; // 提供了两个构造函数 可以构造软引用 public SoftReference(T referent) { super(referent); this.timestamp = clock; } public SoftReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); this.timestamp = clock; } // 获取此软引用的对象 public T get() { T o = super.get(); if (o != null && this.timestamp != clock) this.timestamp = clock; return o; } }
软引用是除了强引用外最强的引用类型。可以通过java.lang.ref.SoftReference使用软引用。
SoftReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。
也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。一旦垃圾线程回收该Java对象之后,get()方法将返回null。
很多初学者不太能理解这块:一言以蔽之,它就是创建一个SoftReference对象,然后这个对象里面包装了obj(强引用对象),也就是对它实施了管理,最终实际存在的是两个对象哦(SoftReference自己变成了强引用,请务必注意这点)~
public class Main { public static void main(String[] args) throws InterruptedException { Person obj = new Person("fsx", 18); SoftReference sf = new SoftReference<>(obj); obj = null; //置为null 让obj被垃圾回收期回收 //byte[] bytes = new byte[1024 * 100]; //System.gc(); //TimeUnit.SECONDS.sleep(1); // 把问题放大,让gc过来回收 确保GC肯定会执行 System.out.println("是否被回收:" + sf.get()); } }
运行时请调整虚拟机参数为:-Xmx2m -Xms2m规定堆内存大小为2m。
运行结果:
是否被回收:Person{name='fsx', age=18}
结果说明:当obj=null后,这时,Obj对象就只剩下软引用了,是软可达的。所以哪怕这个时候调用System.gc();也是不会被回收的(=null后置表示失去了obj这个引用,但是sf还软引用着你呢~~~)
打开被注释掉的new byte[1024*100]语句:这条语句请求一块大的堆空间,使堆内存使用紧张。并显式的再调用一次GC,结果如下:
是否被回收:null
obj对象已经被回收,软可达已经变成了不可达,这就是软引用的效果~
此处不知你是否还能看出一个问题:
obj被gc回收后,SoftReference sf这个对象就成垃圾了,完全没用了,这个时候建议你处理掉它,否则可能造成内存泄漏现象(得不偿失呀)。所以ReferenceQueue队列上场啦(重要:生产环境中一般建议配合队列一起使用):
ReferenceQueue队列和JVM对象垃圾回收机制有关,垃圾回收器将已注册的引用对象添加到队列中,ReferenceQueue实现了入队(enqueue)和出队(poll),还有remove操作,内部元素head就是泛型的Reference