GC基本原理

GC (Garbage Collection)的基本原理:将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停

(1)对新生代的对象的收集称为minor GC;

(2)对旧生代的对象的收集称为Full GC;

(3)程序中主动调用System.gc()强制执行的GC为Full GC。

不同的对象引用类型, GC会采用不同的方法进行回收,JVM对象的引用分为了四种类型:

(1)强引用:默认情况下,对象采用的均为强引用(这个对象的实例没有其他对象引用,GC时才会被回收)

(2)软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的情况下才会被GC)

(3)弱引用:在GC时一定会被GC回收

(4)虚引用:由于虚引用只是用来得知对象是否被GC

强引用

强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器宁愿抛出OOM(OutOfMemoryError)也不会回收它。

说明

不要被这个强字吓到,以为这个引用就很厉害,其实强引用就是程序中使用的一般引用类型。举个简单的例子:

String s = new String("Hello world!");

强可达

如果一个对象与GC Roots之间存在强引用,则称这个对象为强可达(strong reachable)对象。

当你声明一个变量并指向一个实例的时候,其实就是在创造一个强引用。

那么,既然叫强引用,它“强”在哪里呢?

这主要体现在JVM进行GC的时候,只要对象有强引用与其关联,就绝对不会对它进行回收,即使已经内存不足了也不会收回有强引用指向的对象。

如果你不需要使用某个对象了,可以将相应的引用设置为null,消除强引用来帮助垃圾回收器进行回收。因为过多的强引用也是导致OOM的罪魁祸首。

s = null;

显式地设置消除引用,或已超出对象的生命周期范围,则JVM会认为该对象不存在引用,这时就可能会回收这个对象。但是具体什么时候收集这要取决于具体的GC算法。

如果在一个方法的内部有一个变量s持有一个对象(Object)的强引用,那么这个变量s保存在栈中,而真正的引用内容(object)保存在堆中。当这个方法运行完成后就会退出方法栈,则引用s也会被销毁,这个object就可能会在之后的一次GC中回收。但是当这个s是全局变量时,就需要在不再使用这个对象时将引用s赋值为null,也就是消除与object对象之间的强引用,因为有强引用关联的对象是不会被垃圾回收的。

下面看另一个例子:

A a = new A();
B b = new B(a);
a = null;

这里a和b都持有一个对象的强引用,当执行 a = null 时, a 不再持有 A 的强引用。讲道理,A 已经该被回收了。但是这里a = null 时,A 对象不满足被回收的条件,因为还有一个B对象持有其强引用,这时候就会造成内存泄漏。

再看另一个会导致内存泄漏的例子:

public static ArrayList<Object> list = new ArrayList<Object>();
public void stackOverflowTest(Object object){
list.add(object);
object = null;
}

GC回收的是不可达对象,但是,在这个静态集合类对象中,持有了对象的强引用,但却有可能其中的某些对象已经不再使用了,所以当非静态对象被静态变量持有强引用的时候,最容易发生内存泄露。

在方法中从list获取到对象后赋值给一个变量,使用完之后将这个变量设置为null并不会释放object引用的对象,因为list中还是持有对象的强引用。这时就造成了内存泄漏。

小结

所以小结一下强引用的特点:

  • 强引用就是最普通的引用
  • 可以使用强引用直接访问目标对象
  • 强引用指向的对象在任何时候都不会被系统回收
  • 强引用可能会导致内存泄漏
  • 过多的强引用会导致OOM

软引用

软引用是使用SoftReference创建的引用,强度弱于强引用,被其引用的对象在内存不足的时候会被回收,不会产生内存溢出。

说明

软引用,顾名思义就是比较“软”一点的引用。

当一个对象与GC Roots之间存在强引用时,无论何时都不会被GC回收掉。如果一个对象与GC Roots之间没有强引用与其关联而存在软引用关联时,那么垃圾回收器对它的态度就取决于内存的紧张程度了。如果内存空间足够,垃圾回收器就不会回收这个对象,但如果内存空间不足了,它就难逃被回收的厄运。

软可达

如果一个对象与GC Roots之间不存在强引用,但是存在软引用,则称这个对象为软可达(soft reachable)对象。

在垃圾回收器没有回收它的时候,软可达对象就像强可达对象一样,可以被程序正常访问和使用,但是需要通过软引用对象间接访问,需要的话也能重新使用强引用将其关联。所以软引用适合用来做内存敏感的高速缓存。

String s = new String("hello world");    // 创建强引用与String对象关联,现在该String对象为强可达状态
SoftReference<String> softRef = new SoftReference<String>(s); // 再创建一个软引用关联该对象
s = null; // 消除强引用,现在只剩下软引用与其关联,该String对象为软可达状态
s = softRef.get(); // 重新关联上强引用

这里变量s持有对字符串对象的强引用,而softRef持有对该对象的软引用,所以当执行s = null后,字符串对象就只剩下软引用了,这时如果因为内存不足发生Full GC,就会把这个字符串对象回收掉。

注意

在垃圾回收器回收一个对象前,SoftReference类所提供的get方法会返回Java对象的强引用,一旦垃圾线程回收该对象之后,get方法将返回null。所以在获取软引用对象的代码中,一定要先判断返回是否为null,以免出现NullPointerException异常而导致应用崩溃。

下面的代码会让s再次持有对象的强引用:

s = softRef.get();

如果在softRef指向的对象被回收前,用强引用指向该对象,那这个对象又会变成强可达。

来看一个使用SoftReference的例子:

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List; public class TestA {
static class OOMClass{
private int[] oom = new int[1024 * 100];// 100KB
} public static void main(String[] args) throws InterruptedException {
List<SoftReference> list = new ArrayList<>();
while(true){
for (int i = 0; i < 100; i++) {
list.add(new SoftReference<OOMClass>(new OOMClass()));
}
Thread.sleep(500);
}
}
}

设置一下虚拟机参数:

-verbose:gc -Xms4m -Xmx4m -Xmn2m

运行结果:

[GC (Allocation Failure)  1017K->432K(3584K), 0.0017239 secs]
[GC (Allocation Failure) 1072K->472K(3584K), 0.0099237 secs]
[GC (Allocation Failure) 1323K->1296K(3584K), 0.0009528 secs]
[GC (Allocation Failure) 2114K->2136K(3584K), 0.0009951 secs]
[Full GC (Ergonomics) 2136K->1992K(3584K), 0.0040658 secs]
[Full GC (Ergonomics) 2807K->2791K(3584K), 0.0036280 secs]
[Full GC (Allocation Failure) 2791K->373K(3584K), 0.0032477 secs]
[Full GC (Ergonomics) 2786K->2773K(3584K), 0.0034554 secs]
[Full GC (Allocation Failure) 2773K->373K(3584K), 0.0032667 secs]
[Full GC (Ergonomics) 2798K->2775K(3584K), 0.0036231 secs]
[Full GC (Allocation Failure) 2775K->375K(3584K), 0.0055482 secs]
[Full GC (Ergonomics) 2799K->2776K(3584K), 0.0031358 secs]
...省略n次GC信息

在TestA中,我们使用死循环不断的往list中添加新对象,如果是强引用,会很快因为内存不足而抛出OOM,因为这里的堆内存大小设置为了4M,而一个对象就有100KB,一个循环添加100个对象,也就是差不多10M,显然一个循环都跑不完就会内存不足,而这里,因为使用的是软引用,所以JVM会在内存不足的时候将软引用回收掉。

[Full GC (Allocation Failure) 2791K->373K(3584K), 0.0032477 secs]

从这一条可以看出,在内存不足发生Full GC时,回收掉了大部分的软引用指向的对象,释放了大量的内存。

因为这里新生代只分配了2M,所以很快就会发生GC,如果你的程序运行没有看到这个结果,请先确认一下虚拟机参数是否设置正确,如果设置正确还是没有看到,那么将循环次数由1000改为10000或者100000在试试看。

应用场景

软引用关联的对象,只有在内存不足的时候JVM才会回收该对象。这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。

现在考虑这样一个场景 ,在很多应用中,都会出现大量的默认图片,比如说QQ的默认头像,应用内的默认图标等等,这些图片很多地方会用到。

如果每次都去读取图片,由于读取文件速度较慢,大量重复的读取会导致性能下降。所以可以考虑将图片缓存起来,需要的时候直接从内存中读取。但是,由于图片占用内存空间比较大,缓存的图片过多会占用比较多的内存,就可能比较容易发生OOM。这时候,软引用就派得上用场了。

注意

SoftReference对象是用来保存软引用的,但它同时也是一个Java对象。所以,当软可及对象被回收之后,虽然这个SoftReference对象的get()方法返回null,但SoftReference对象本身并不是null,而此时这个SoftReference对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量SoftReference对象带来的内存泄漏。

ReferenceQueue就是用来保存这些需要被清理的引用对象的。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

下面用SoftReference来实现一个简单的缓存类:

public class SoftCache<T> {
// 引用队列
private ReferenceQueue<T> referenceQueue = new ReferenceQueue<>();
// 保存软引用集合,在引用对象被回收后销毁
private List<Reference<T>> list = new ArrayList<>(); // 添加缓存对象
public synchronized void add(T obj){
// 构建软引用
Reference<T> reference = new SoftReference<T>(obj, referenceQueue);
// 加入列表中
list.add(reference);
} // 获取缓存对象
public synchronized T get(int index){
// 先对无效引用进行清理
clear();
if (index < 0 || list.size() < index){
return null;
}
Reference<T> reference = list.get(index);
return reference == null ? null : reference.get();
} public int size(){
return list.size();
} @SuppressWarnings("unchecked")
private void clear(){
Reference<T> reference;
while (null != (reference = (Reference<T>) referenceQueue.poll())){
list.remove(reference);
}
}
}

然后测试一下这个缓存类:

public class SoftCacheTest {
private static int num = 0; public static void main(String[] args){
SoftCache<OOMClass> softCache = new SoftCache<>();
for (int i = 0; i < 40; i++) {
softCache.add(new OOMClass("OOM Obj-" + ++num));
}
System.out.println(softCache.size());
for (int i = 0; i < softCache.size(); i++) {
OOMClass obj = softCache.get(i);
System.out.println(obj == null ? "null" : obj.name);
}
System.out.println(softCache.size());
} static class OOMClass{
private String name;
private int[] oom = new int[1024 * 100];// 100KB public OOMClass(String name) {
this.name = name;
}
}
}

仍使用之前的虚拟机参数:

-verbose:gc -Xms4m -Xmx4m -Xmn2m

运行结果:

[GC (Allocation Failure)  1017K->432K(3584K), 0.0012236 secs]
[GC (Allocation Failure) 1117K->496K(3584K), 0.0016875 secs]
[GC (Allocation Failure) 1347K->1229K(3584K), 0.0015059 secs]
[GC (Allocation Failure) 2047K->2125K(3584K), 0.0018090 secs]
[Full GC (Ergonomics) 2125K->1994K(3584K), 0.0054759 secs]
[Full GC (Ergonomics) 2822K->2794K(3584K), 0.0023167 secs]
[Full GC (Allocation Failure) 2794K->376K(3584K), 0.0036056 secs]
[Full GC (Ergonomics) 2795K->2776K(3584K), 0.0042365 secs]
[Full GC (Allocation Failure) 2776K->376K(3584K), 0.0035122 secs]
[Full GC (Ergonomics) 2795K->2776K(3584K), 0.0054760 secs]
[Full GC (Allocation Failure) 2776K->376K(3584K), 0.0036965 secs]
[Full GC (Ergonomics) 2802K->2777K(3584K), 0.0044513 secs]
[Full GC (Allocation Failure) 2777K->376K(3584K), 0.0041400 secs]
[Full GC (Ergonomics) 2796K->2777K(3584K), 0.0025255 secs]
[Full GC (Allocation Failure) 2777K->376K(3584K), 0.0037690 secs]
[Full GC (Ergonomics) 2817K->2777K(3584K), 0.0037759 secs]
[Full GC (Allocation Failure) 2777K->377K(3584K), 0.0042416 secs]
缓存列表大小:40
OOM Obj-37
OOM Obj-38
OOM Obj-39
OOM Obj-40
缓存列表大小:4

可以看到,缓存40个软引用对象之后,如果一次性全部存储,显然内存大小无法满足,所以在不断创建软引用对象的过程中,不断发生GC来进行垃圾回收,最终只有4个软引用未被清理掉。

强引用与软引用对比

没有对比就没有伤害,来将强引用和软引用对比一下:

public class Test {

    static class OOMClass{
private int[] oom = new int[1024];
} public static void main(String[] args) {
testStrongReference();
//testSoftReference();
} public static void testStrongReference(){
List<OOMClass> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(new OOMClass());
}
} public static void testSoftReference(){
ReferenceQueue<OOMClass> referenceQueue = new ReferenceQueue<>();
List<SoftReference> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
OOMClass oomClass = new OOMClass();
list.add(new SoftReference(oomClass, referenceQueue));
oomClass = null;
}
}
}

运行testStrongReference方法的结果如下:

[GC (Allocation Failure)  1019K->384K(3584K), 0.0033595 secs]
[GC (Allocation Failure) 1406K->856K(3584K), 0.0013098 secs]
[GC (Allocation Failure) 1880K->1836K(3584K), 0.0014382 secs]
[Full GC (Ergonomics) 1836K->1756K(3584K), 0.0039761 secs]
[Full GC (Ergonomics) 2778K->2758K(3584K), 0.0021269 secs]
[Full GC (Ergonomics) 2779K->2770K(3584K), 0.0016329 secs]
[Full GC (Ergonomics) 2779K->2775K(3584K), 0.0023157 secs]
[Full GC (Ergonomics) 2775K->2775K(3584K), 0.0015927 secs]
[Full GC (Ergonomics) 3037K->3029K(3584K), 0.0025071 secs]
[Full GC (Ergonomics) 3067K->3065K(3584K), 0.0017529 secs]
[Full GC (Allocation Failure) 3065K->3047K(3584K), 0.0033445 secs]
[Full GC (Ergonomics) 3068K->3059K(3584K), 0.0016623 secs]
[Full GC (Ergonomics) 3070K->3068K(3584K), 0.0028357 secs]
[Full GC (Allocation Failure) 3068K->3068K(3584K), 0.0017616 secs]
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid3352.hprof ...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Heap dump file created [3855956 bytes in 0.017 secs]
[Full GC (Ergonomics) 3071K->376K(3584K), 0.0032068 secs]
at reference.Test$OOMClass.<init>(Test.java:11)
at reference.Test.testStrongReference(Test.java:22)
at reference.Test.main(Test.java:15) Process finished with exit code 1

可以看到,很快就抛出了OOM,原因是Java heap space,也就是堆内存不足。

如果运行testSoftReference方法,将会得到如下结果:

[GC (Allocation Failure)  1019K->464K(3584K), 0.0019850 secs]
[GC (Allocation Failure) 1484K->844K(3584K), 0.0015920 secs]
[GC (Allocation Failure) 1868K->1860K(3584K), 0.0043236 secs]
[Full GC (Ergonomics) 1860K->1781K(3584K), 0.0044581 secs]
[Full GC (Ergonomics) 2802K->2754K(3584K), 0.0041726 secs]
[Full GC (Ergonomics) 2802K->2799K(3584K), 0.0031293 secs]
[Full GC (Ergonomics) 3023K->3023K(3584K), 0.0024830 secs]
[Full GC (Ergonomics) 3071K->3068K(3584K), 0.0035025 secs]
[Full GC (Allocation Failure) 3068K->405K(3584K), 0.0040672 secs]
[GC (Allocation Failure) 1512K->1567K(3584K), 0.0011170 secs]
[Full GC (Ergonomics) 1567K->1496K(3584K), 0.0048438 secs]

可以看到,并没有抛出OOM,而是进行多次了GC,可以明显的看到这一条:

[Full GC (Allocation Failure) 3068K->405K(3584K), 0.0040672 secs]

当内存不足时进行了一次Full GC,回收了大部分内存空间,也就是将大部分软引用指向的对象回收掉了。

小结

  • 软引用弱于强引用
  • 软引用指向的对象会在内存不足时被垃圾回收清理掉
  • JVM会优先回收长时间闲置不用的软引用对象,对那些刚刚构建的或刚刚使用过的软引用对象- 会尽可能保留
  • 软引用可以有效的解决OOM问题
  • 软引用适合用作非必须大对象的缓存

java 中的引用类型的更多相关文章

  1. Java中的引用类型(强引用、弱引用)和垃圾回收

    Java中的引用类型和垃圾回收 强引用Strong References 强引用是最常见的引用: 比如: StringBuffer buffer = new StringBuffer(); 创建了一个 ...

  2. java中的引用类型:强软弱虚

    java中的引用类型共4种:强软弱虚,具体每种类型的特点和应用场景.记录下.本文是看了马士兵老师的视频后记录整理的.加深印象. 基本概念 1. 强引用 强引用是使用最普遍的引用.如果一个对象具有强引用 ...

  3. Java中的引用类型和使用场景

    作者:Grey 原文地址:Java中的引用类型和使用场景 Java中的引用类型有哪几种? Java中的引用类型分成强引用, 软引用, 弱引用, 虚引用. 强引用 没有引用指向这个对象,垃圾回收会回收 ...

  4. Java中的引用类型Scanner类和随机类型Random

    Scanner类 我们要学的Scanner类是属于引用数据类型,我们先了解下引用数据类型.   引用数据类型的使用 与定义基本数据类型变量不同,引用数据类型的变量定义及赋值有一个相对固定的步骤或格式. ...

  5. java中的引用类型 部分讲解

    所谓的引用类型 类--接口--数组--枚举 [01--Scanner类] Scanner 这个类是用于键盘输入 它的格式为 类型  对象名称 =  new  类型(): 它的操作格式  对象名.nex ...

  6. java中的引用类型的对象存放在哪里

    根据上下文来确定.比如void func(){    Object obj = new Object();//这个obj在函数的栈里.}class Test{   private Object obj ...

  7. Java中Calender引用类型

    某些时候需要使用深拷贝: Calendar startTime = (Calendar) this._paramModel.getStartTime().clone(); 这样对startTime.a ...

  8. Java中的引用类型

    强引用(Strong) 就是我们平时使用的方式 A a = new A();强引用的对象是不会被回收的 软引用(Soft) 在jvm要内存溢出(OOM)时,会回收软引用的对象,释放更多内存 弱引用(W ...

  9. 判断java中两个对象是否相等

    java中的基本数据类型判断是否相等,直接使用"=="就行了,相等返回true,否则,返回false. 但是java中的引用类型的对象比较变态,假设有两个引用对象obj1,obj2 ...

随机推荐

  1. CSS背景和CSS3背景background属性

    css背景属性用于定义HTML元素的背景 背景属性既可以为单个的单元设置背景,也可以为整个页面设置背景,可以对上述二者的任意组合设置背景,段落.文字.不同状态的链接.图像.内容区域修改其背景样式.设置 ...

  2. input 禁止删除部分文字

    用label和所需的input链接,label部分就是禁止删除的部分.<input type="text" name="city" value=" ...

  3. OpenCV Machine Learning (C++)

    /*M/////////////////////////////////////////////////////////////////////////////////////////// IMPOR ...

  4. 设计模式学习笔记——Bridge 桥接模式

    先说一下我以前对桥接模式的理解:当每个类中都使用到了同样的属性或方法时,应该将他们单独抽象出来,变成这些类的属性和方法(避免重复造轮子),当时的感觉是和三层模型中的model有点单相似,也就是让mod ...

  5. 关于 Visual stdio 编译报错:error MSB6006: “CL.exe”已退出

    网上查看,原因有多种. 1,我自己遇到的是这样的: 环境:VS2019,编译项目 image-master,中间自己重整了原来的目录,移动了很多文件.编译报错:error MSB6006: “CL.e ...

  6. GTID复制模式切换与传统主从复制间切换

    GTID复制模式切换到传统主从复制主从复制环境:主库:10.18.10.11从库:10.18.10.12MySQL5.7.22 切换之前查看下主从gitd_mode参数值主服务器:gtid_mode值 ...

  7. 文件格式-CVS:CVS

    ylbtech-文件格式-CVS:CVS 逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文 ...

  8. HttpClient实现通过url下载文件

    其实就是通过浏览器url,点击就会下载文件. 这里是从代码层面上,对文件进行下载. package main.java.com.abp.util; import org.apache.http.*; ...

  9. 自动化生成 Openstack 新项目开发框架

    目录 目录 前言 环境 openstack-project-generator 前言 Openstack Developer 应该都知道, 开发一个 Openstack 的新项目并不是一个从 0 到 ...

  10. RNN系列

    漫谈RNN之梯度消失及梯度爆炸:http://bbs.imefuture.com/article/4405 漫谈RNN之长短期记忆模型LSTM:http://bbs.imefuture.com/art ...