1. 如何判断对象可以回收
1.1. 引用计数法
在对象中添加一个引用计数器,每当有一个地方引用它是,计数器值就加一;但引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能在被使用的。这种算法简单,高效,但是不能处理相互引用的情况,比如下图所示:
A对象与B对象相互引用,但是除此之外没有任何对象指向A和B,这种情况引用计数法就不能处理了。
1.2. 可达性分析算法
- Java虚拟机中的垃圾回收器采用可达性分析算法来探索所有存活的对象。
- 扫描堆中的对象,看是否能够沿着GC Root对象为起点的引用链找到该对象,找不到,表示可以回收
- 那些对象可以作为GC Root?(通过工具Memory Analyzer(mat)**)
1.2.1. 使用mat来分析可达性
测试代码: ```bash import java.io.IOException; import java.util.ArrayList;
/**
- 演示GC Roots
- @author : gnehcgnaw
@since : 2020/4/11 18:09 */ public class Demo1 { public static void main(String[] args) throws IOException {
ArrayList<String> arrayList = new ArrayList<>() ;
arrayList.add("a");
arrayList.add("b");
System.out.printf("1");
System.in.read();
arrayList = null ;
System.out.printf("2");
System.in.read();
System.out.printf("end");
} } ```
使用JPS查看Java进程
➜ chapter2 git:(master) ✗ jps
20049
20137 RemoteMavenServer36
20254 Launcher
20255 Demo1
20367 Jps
使用jmp工具dump堆,(分两次dump,第一次是程序执行到输出1的时候,第二次是程序执行到输出2的时候)
# 控制台输出1的时候
jmap -dump:format=b,live,file="/Users/gnehcgnaw/Desktop/demo1_1.bin" 20255
# 控制台输出2的时候
jmap -dump:format=b,live,file="/Users/gnehcgnaw/Desktop/demo1_2.bin" 20255
将文件导入到mat工具中
- 点击图示中的GC Root,具体如下所示:
- 展开之后文件1和文件2的对比图:
总结:
在Java技术体系里面,固定作为GC Roots的对象包括如下几种:
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等;
- 在方法区中类静态属性引用的对象,比如Java类的引用类型静态变量;
- 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用;
- 在本地方法栈中的JNI(即通常说的Native方法)引用对象;
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如:NullPointException、OutOfMemoryError)等,还有系统类加载器;
- 所有被同步锁(synchronized关键字)持有的对象。
- 反应Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
1.3. 四种引用
无论是通过引用计数法判断对象的引用数量,还是通过可达性分析算法判断对象是否引用链可达,判断对象是否存活都和“引用”离不开关系。
1.3.1. 强引用(Strongly Reference)
强引用是最传统的“引用”定义,是指在程序代码中普遍存在的引用赋值,即类似“Object object = new Object()”,这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象,只有都断开的情况下才会被垃圾回收机制回收,例如下图所示:【此时的A1对象就可以被回收了。】
A1对象被回收之后:
总结:
只有所有的GC Roots对象都不通过【强引用】引用该对象,该对象才能被垃圾回收。
1.3.2. 软引用(Soft Reference)和弱引用(Weak Reference)
软引用和弱引用相差不大,故而放在一起来说明。
只要A2和A3对象没有被直接的强引用对象B引用所引用,那么它们当垃圾回收时都可能被回收。
举例:强引用C对象经过软引用或者弱引用对象间接的引用了A2和A3对象,但是又被强引用对象B所引用,那么A2和A3是不能被垃圾回收的;
- 假如如下图所示B对象和A2对象直接的引用对象断开,在以下情况下A2对象是有可能被回收的:
- 当垃圾回收时,并且回收之后内存不够时,就会把软引用所引用的这个对象释放掉(JVM认为软引用引用的对象不重要,但内存不足时我需要把它占用的空间腾出来,给更有用的对象去使用)。
- B与A3没有强引用,在垃圾回收时,不管内存够不够都会回收掉弱引用引用的A3对象。这也是软引用和弱引用的区别所在。
软、弱引用还可以配合一个引用队列来一起工作:
- 当软引用引用的对象被垃圾回收时,软引用自身也是一个对象,如果当时配合了引用队列的话,会进入到引用队列中;
- 当弱引用引用的对象也被垃圾回收时,弱引用如果但是配合了引用队列的话,也会进入到引用队列中。
如下图所示:
那为什么要这么做呢?(即为什么要将软引用对象和弱引用对象放在引用队列中呢?)
就是因为软引用也好,弱引用也好,它们自身也要占用一定的内存,如果你需要对它们自身占用的内存做释放,那么就需要使用引用队列来找到它俩,对它们做进一步的处理,比如说它俩还被强引用所引用,只需要一次遍历
把它俩占用的内存释放掉。
总结:
- 软引用
- 仅当软引用引用该对象是,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象;
- 可以配合引用队列来释放软引用自身。
弱引用
- 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象;
- 可以配合引用队列来释放弱引用自身。
1.3.4. 虚引用(Phantom Reference)和终结器引用(FinalReference)
虚引用和终结器引用与软、弱引用不同,软、弱引用既可以配合引用队列来使用,也可以不配合引用队列来使用,而虚引用和终结器应用必须配合引用队列来使用。也就是说当虚引用对象创建的时候就要关联一个引用队列,当终结器引用对象创建的时候它也就要关联一个引用队列。
那它们是怎么工作的呢?1.3.4.1. 虚引用
举例:ByteBuffer申请直接内存
1.3.4.2. 终结器引用(不推荐使用)
所有的Java对象,都会继承Object父类,而Object父类中有一个finallize()方法,当A4这个对象重写了终结方法,并且B对象没有强引用A4,那么A4就会被垃圾回收,那终结方法什么时候被调用呢?**它就是靠终结器引用对象达到调用finallize()方法的。
总结:**
虚引用
- 必须配合引用队列使用,主要配合ByteBuffer使用,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚引用相关方法释放直接内存。
- 终结器引用
- 强引用示例 ```bash import java.util.ArrayList;
/**
- 强引用示例
- -Xmx20m -XX:+PrintGCDetails -verbose:gc
- @author : gnehcgnaw
@since : 2020/4/12 11:39 / public class Demo2 { private static final int _4MB = 10241024*4 ; public static void main(String[] args) {
int j = 0;
ArrayList<byte[]> arrayList = new ArrayList<>() ;
try {
for (int i = 0; i < 100 ; i++) {
arrayList.add(new byte[_4MB]);
j++ ;
}
}catch (Throwable throwable){
throwable.printStackTrace();
}finally {
System.out.println(j);
}
} }
运行结果:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/285619/1586674960471-2e7efbaa-8eef-4a59-8c03-51b88358e3ce.png#align=left&display=inline&height=172&margin=%5Bobject%20Object%5D&name=image.png&originHeight=411&originWidth=1785&size=106316&status=done&style=shadow&width=746)<br />因为空间不够进行了年轻代的空间回收,但是还是不够,就进行了Full GC,但是最后还是产生了:堆溢出。
2. 软引用示例
```bash
import java.lang.ref.SoftReference;
import java.util.ArrayList;
/**
* 软引用示例
* -Xmx20m -XX:+PrintGCDetails -verbose:gc
* @author : <a href="mailto:gnehcgnaw@gmail.com">gnehcgnaw</a>
* @since : 2020/4/12 11:39
*/
public class Demo3 {
private static final int _4MB = 1024*1024*4 ;
public static void main(String[] args) {
int j = 0;
ArrayList<SoftReference<byte[]>> softReferenceArrayList = new ArrayList<>();
try {
for (int i = 0; i < 10 ; i++) {
softReferenceArrayList.add(new SoftReference<>(new byte[_4MB]));
j++ ;
}
for (SoftReference softReference :softReferenceArrayList) {
System.out.println(softReference.get());
}
}catch (Throwable throwable){
throwable.printStackTrace();
}finally {
System.out.println(j);
}
}
}
运行结果:
程序没有报错,但是发现list集合中有null值,这是因为当空间不足的时候首先会进行年轻代的GC,然后是Full GC,这时会将软引用指向的空间进行清理,发现最后只存放了2个元素,但其实软引用对象本身也占用空间,那这部分空间如何处理呢?需要软引用和引用队列相结合的方式。
- 软引用和引用队列相结合 ```bash import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.ArrayList;
/**
- 软引用配合引用队列
- -Xmx20m -XX:+PrintGCDetails -verbose:gc
- @author : gnehcgnaw
@since : 2020/4/12 13:03 / public class Demo4 { private static final int _4MB = 10241024*4 ;
public static void main(String[] args) {
int j = 0 ; ArrayList<SoftReference<byte[]>> softReferenceArrayList = null ; try{ softReferenceArrayList = new ArrayList<>(); //引用队列的泛型要与软引用保持一直 ReferenceQueue<byte[]> softReferenceReferenceQueue = new ReferenceQueue<>(); for (int i = 0; i <10; i++) { //关联引用队列,当软引用关联的byte[]被回收时,软引用自己会加入到queue中去。 SoftReference<byte[]> softReference = new SoftReference<>(new byte[_4MB],softReferenceReferenceQueue); softReferenceArrayList.add(softReference); j++ ; } //从队列中出对一个软引用 Reference<? extends byte[]> reference = softReferenceReferenceQueue.poll(); while (reference!=null){ //这里的reference在list中都是无效的,故而需要从list中清理掉 softReferenceArrayList.remove(reference); //出对下一个 reference = softReferenceReferenceQueue.poll(); } for (SoftReference softReference : softReferenceArrayList){ System.out.println(softReference.get()); } }catch (Throwable throwable){ throwable.printStackTrace(); }finally { System.out.println("j = " + j); System.out.println("softReferenceArrayList's size is "+ softReferenceArrayList.size()); }
}
}
运行结果:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/285619/1586676803906-4255add8-af00-4c42-bebf-c5fee6ad87c0.png#align=left&display=inline&height=241&margin=%5Bobject%20Object%5D&name=image.png&originHeight=554&originWidth=1715&size=139991&status=done&style=shadow&width=746)
<a name="zze2h"></a>
#### 1.3.5.2. 弱引用示例
```bash
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
* -Xmx10m -XX:+PrintGCDetails -verbose:gc
* 弱引用示例
* @author : <a href="mailto:gnehcgnaw@gmail.com">gnehcgnaw</a>
* @since : 2020/4/12 15:43
*/
public class Demo5 {
private final static int _4MB = 1024*1024*4 ;
public static void main(String[] args) {
try{
ArrayList<WeakReference<byte[]>> weakReferenceArrayList = new ArrayList<>() ;
ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<>() ;
for (int i = 0; i <10 ; i++) {
WeakReference<byte[]> weakReference = new WeakReference<>(new byte[_4MB], referenceQueue);
weakReferenceArrayList.add(weakReference);
}
Reference<? extends byte[]> poll = referenceQueue.poll();
while (poll!=null){
weakReferenceArrayList.remove(poll);
poll = referenceQueue.poll() ;
}
for (WeakReference weakReference : weakReferenceArrayList){
System.out.println(weakReference.get());
}
}catch (Exception exception){
exception.printStackTrace();
}
}
}
- 【待解决】**将内存调整到很小还是会发生内存溢出,原因是软引用对象本身也占用空间,那这种问题怎么解决呢?**