一、根搜索算法
把内存中的每个对象看作一个节点,定义对象作为根节点“GC Roots”。如果一个对象有另一个对象引用,则认为这个对象有一条指向另一个对象的边。
二、根节点(GC Roots)类型
三、JVM 中的引用类型
JVM 中主要分为四种引用类型,分别是“强引用”、“软引用”、“弱引用”、“幽灵引用”,接下来就说明这四种引用。
(1)强引用
使用关键 new 生成的对象的引用,如 Object obj = new Object(),只要 obj 对象生命周期未结束,或者没有显示把 obj 赋为 null,JVM 不会进行回收。
当内存容量不足时,宁愿出现 OOM 错误,也要一直保存的引用,不会对其对象进行回收。强引用是 Java 默认支持的模式,只要栈内存中还有指向堆内存的引用,则当内存不足时,只会抛出 OutOfMemoryError 错误,不会对对象进行回收。
因强引用是我们开发一直使用的模式,并且具有这样的异常错误产生,所以,尽量少实例化对象,避免因无法回收对象造成内存溢出。
代码示例如下:
/**
* 强引用代码示例
* @author andy
*/
public class StrongReferenceDemo {
private static Logger logger = LoggerFactory.getLogger(StrongReferenceDemo.class);
public static void main(String[] args) {
// 对象声明
Object object = new Object();
// 垃圾回收前对象日志打印
logger.info("垃圾回收前对象日志打印:{}", object);
// 执行垃圾回收
System.gc();
// 垃圾回收后进行对象日志打印
// 打印结果显示对象依然存在
logger.info("垃圾回收后进行对象日志打印:{}", object);
}
}
复制代码
程序执行结果如下:
INFO StrongReferenceDemo - 垃圾回收前对象日志打印:java.lang.Object@5123a213
INFO StrongReferenceDemo - 垃圾回收后进行对象日志打印:java.lang.Object@5123a213
复制代码
由此测试可知,强引用的对象是不会被垃圾收集器回收的。
(2)软引用
当内存不足时,才会对软引用对象进行回收,常应用于高速缓存。Mybatis 持久层开发框架使用的高速缓存,采用的便是软引用方式。
对于当下的开源项目,使用软引用作为项目的缓存组件。当内存充足时,不进行对象回收;反之,则进行对象回收。软引用使用 java.lang.ref.SoftReference 类实现,这里说明一下,所有的引用类都在 java.lang.ref 包下。
SoftReference 类的主要使用方法如下:
示例代码如下:
/**
* 软引用代码示例
* @author andy
*/
public class SoftReferenceDemo {
private static Logger logger = LoggerFactory.getLogger(SoftReferenceDemo.class);
public static void main(String[] args) {
// 初始化对象
String softReferenceStr = "soft reference";
SoftReference<String> softReference = new SoftReference<>(softReferenceStr);
// 为了体现软引用,将 softReferenceStr 赋值为空
softReferenceStr = null;
logger.info("垃圾回收前的软引用对象地址:{}", softReference.get());
// 为了体现软引用被垃圾回收器回收,借助另一个对象进行持续性赋值,最后抛出内存异常错误
// 这样的情况下,模拟出最后内存不够,进而进行 GC 操作
String str = "Garbage";
try {
while (true) {
str += str;
}
} catch (Throwable e) {
// 由于内存溢出是Error错误,故而以捕获 Throwable 接口实现类为主
logger.error("内存溢出异常", e);
} finally {
System.gc();
try {
// 垃圾回收需要时间,故而等待 10 s
Thread.sleep(100000);
} catch (Exception e) {
logger.error("线程类延时程序异常", e);
}
logger.info("软引用垃圾回收处理后对象地址:{}", softReference.get());
}
}
}
复制代码
程序执行结果日志如下:
INFO SoftReferenceDemo - 垃圾回收前的软引用对象地址:soft reference
ERROR SoftReferenceDemo - 内存溢出异常
java.lang.OutOfMemoryError: Java heap space
at java.base/java.util.Arrays.copyOfRange(Arrays.java:4030)
at java.base/java.lang.StringLatin1.newString(StringLatin1.java:715)
at java.base/java.lang.StringBuilder.toString(StringBuilder.java:448)
at com.ailpha.practice.reference.SoftReferenceDemo.main(SoftReferenceDemo.java:31)
INFO SoftReferenceDemo - 软引用垃圾回收处理后对象地址:soft reference
复制代码
这里先留下一个小问题,留待未来进行处理。
(3)弱引用
无论内存是否充足,只要出现了垃圾,便会对其进行回收处理。可以这么理解,简直是“弱不禁风,弱爆了”。
当内存进行 GC 处理时,弱引用对象便会进行垃圾回收,因此,不建议使用。一旦发生 GC,则必须进行对象处理,很容易数据丢失。对于弱引用的实现,可以采用以下类方式:java.util.WeakHashMap 和 WeakReference。
代码示例 1:
/**
* 弱引用代码示例
* @author andy
*/
public class WeakReferenceDemo {
private static Logger logger = LoggerFactory.getLogger(WeakReferenceDemo.class);
public static void main(String[] args) {
// 初始化对象
String key = new String("stack");
String value = "heap";
// 弱引用使用 WeakHashMap 类实现
Map<String,String> weakReference = new WeakHashMap<>();
weakReference.put(key,value);
// 垃圾回收前的对象情况
logger.info("弱引用的对象情况:{}",weakReference);
// 对key进行赋值为 null
key = null;
logger.info("垃圾回收前的弱引用的对象情况:{}",weakReference);
// 垃圾回收查看弱引用对象情况
System.gc();
logger.info("垃圾回收后的弱引用的对象情况:{}",weakReference);
// 延伸思考,new String()和直接赋值结果是否相同?
// String key = "stack";
// String value = "heap";
}
}
复制代码
执行结果如下:
INFO WeakReferenceDemo - 弱引用的对象情况:{stack=heap}
INFO WeakReferenceDemo - 垃圾回收前的弱引用的对象情况:{stack=heap}
INFO WeakReferenceDemo - 垃圾回收后的弱引用的对象情况:{}
复制代码
代码示例 2:
/**
* 弱引用代码示例
* @author andy
*
*/
public class WeakReferenceDemo2 {
private static Logger logger = LoggerFactory.getLogger(WeakReferenceDemo2.class);
public static void main(String[]atgs){
// 对象初始化
String str = new String("weakReference");
// 使用 WeakReference 类实现弱引用
WeakReference<String> weakReference = new WeakReference<>(str);
logger.info("弱引用的对象情况:{}", weakReference.get());
// 对象赋值为空,解除强引用
str = null;
logger.info("垃圾回收前的弱引用的对象情况:{}", weakReference.get());
// 垃圾回收
System.gc();
logger.info("垃圾回收后的弱引用的对象情况:{}", weakReference.get());
// 延伸思考,new String()和直接赋值,结果是否相同?
// String str = "weakReference";
}
}
复制代码
执行结果如下:
INFO WeakReferenceDemo2 - 弱引用的对象情况:weakReference
INFO WeakReferenceDemo2 - 垃圾回收前的弱引用的对象情况:weakReference
INFO WeakReferenceDemo2 - 垃圾回收后的弱引用的对象情况:null
复制代码
使用弱引用需要注意以下几点:
1、弱引用不建议使用,就是因为一旦发生 GC,就会被清空;
2、使用字符串举例,需要小心对象自动入池的问题,一旦入池,是无法清空的,所以,建议使用 new String()方式;
3、字符串对象一旦声明,不可修改。
(4)幽灵引用(虚引用)
幽灵引用相当于没有引用。
永远得不到的数据叫做幽灵引用,更多意义在于提出了一种思想。所有保存在幽灵引用中的数据都不会真正保留。对象无法回收,finalize() 方法无法执行。幽灵引用可以使用以下类实现:java.lang.ref.PhantomReference
/**
* 幽灵引用代码示例
* @author andy
*/
public class PhantomReferenceDemo {
private static Logger logger = LoggerFactory.getLogger(PhantomReferenceDemo.class);
public static void main(String[] args) throws Exception{
// 对象初始化
String str = new String("PhantomReference");
// 引用队列
ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
// 幽灵引用
PhantomReference<String> ref = new PhantomReference<>(str,referenceQueue);
logger.info("引用队列的对象情况:{}", referenceQueue.poll());
// 垃圾回收
System.gc();
logger.info("垃圾回收后的引用队列的对象情况:{}", referenceQueue.poll());
}
}
复制代码
执行结果如下:
INFO PhantomReferenceDemo - 引用队列的对象情况:null
INFO PhantomReferenceDemo - 垃圾回收后的引用队列的对象情况:null
复制代码
四、引用队列
引用队列就是对要回收的对象进行保存,在对象回收前可以进行操作处理。对于对象的回收都是从根对象开始向下扫描的。对于 GC 而言,想要确定哪些对象被回收,确定好引用的强度即引用路径的设定。以下展示引用路径。
对于对象的引用,要考虑到引用的关联,因此必须找到强关联。爲了避免非强关联对象所带来的内存引用的问题,故而提出了引用队列的概念。如果创建软引用和弱引用使用了引用队列,那么,在引用对象回收前会保存到引用队列之中。引用队列的主要作用就是对被回收对象的控制。可以使用 java.lang.ref.ReferenceQueue 类实现。
代码示例如下:
/**
* 引用队列代码示例
* @author andy
*/
public class ReferenceQueueDemo {
private static Logger logger = LoggerFactory.getLogger(ReferenceQueueDemo.class);
public static void main(String[]atgs) throws Exception{
// 对象初始化
String str = new String("referenceQueue");
// 引用队列
ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
WeakReference<String> ref = new WeakReference<>(str, referenceQueue);
// 引用队列情况
logger.info("引用队列的对象情况:{}", referenceQueue.poll());
// 对象赋值为null
str = null;
logger.info("垃圾回收前的引用队列的对象情况:{}", referenceQueue.poll());
// 垃圾处理
System.gc();
Thread.sleep(100);
logger.info("垃圾回收后的引用队列的对象情况:{}", referenceQueue.poll());
// 延伸思考,new String()和直接赋值的结果是否相同?
// String obj = "referenceQueue";
}
}
复制代码
执行结果如下:
INFO ReferenceQueueDemo - 引用队列的对象情况:null
INFO ReferenceQueueDemo - 垃圾回收前的引用队列的对象情况:null
INFO ReferenceQueueDemo - 垃圾回收后的引用队列的对象情况:java.lang.ref.WeakReference@6d3af739
复制代码
五、总结
本文主要是对 JVM 中的引用类型进行相应的解释分析,并附上了代码示例。以上介绍的引用,排除强引用之外,虽然在很多开发场景下不怎么使用到,但是,这对于理解 JVM 的机制以及引用机制还是有帮助的。
这里可以做一个思考延伸,问题如下:
1、String 字符串如何存储?构造方法(new String(""))和直接赋值("")有何区别?
2、1、WeakHashMap 和 HashMap 的区别?
评论