JVM:Java中的引用
JVM:Java中的引用
本笔记是根据bilibili上 尚硅谷 的课程 Java大厂面试题第二季 而做的笔记
在原来的时候,我们谈到一个类的实例化
Person p = new Person()
在等号的左边,就是一个对象的引用,存储在栈中
而等号右边,就是实例化的对象,存储在堆中
其实这样的一个引用关系,就被称为强引用
整体架构
强引用
当内存不足的时候,JVM 开始垃圾回收,对于强引用的对象,就算是出现了 OOM 也不会对该对象进行回收,打死也不回收!
强引用是我们最常见的普通对象引用,只要还有一个强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到,JVM 也不会回收,因此强引用是造成Java内存泄漏的主要原因之一。
对于一个普通的对象,如果没有其它的引用关系,只要超过了引用的作用域或者显示地将相应(强)引用赋值为null,一般可以认为就是可以被垃圾收集的了
当然具体回收时机还是要看垃圾回收策略
强引用小例子:
public class StrongReferenceDemo {
public static void main(String[] args) {
// 这样定义的默认就是强应用
Object obj1 = new Object();
// 使用第二个引用,指向刚刚创建的Object对象
Object obj2 = obj1;
// 置空
obj1 = null;
// 垃圾回收
System.gc();
System.out.println(obj1); // null
System.out.println(obj2); // java.lang.Object@1b6d3586
}
}
输出结果我们能够发现,即使 obj1
被设置成了 null,然后调用 gc 进行回收,但是也没有回收实例出来的对象,obj2
还是能够指向该地址,也就是说垃圾回收器,并没有将该对象进行垃圾回收。
注意:
System.gc();
调用该函数,只是建议 JVM 可以进行垃圾回收了,但是不是一定会发生垃圾回收
软引用
软引用是一种相对弱化了一些的引用,需要用Java.lang.ref.SoftReference
类来实现,可以让对象豁免一些垃圾收集,对于只有软引用的对象来讲:
- 当系统内存充足时,它不会被回收
- 当系统内存不足时,它会被回收
软引用通常在对内存敏感的程序中,比如高速缓存就用到了软引用,内存够用的时候就保留,不够用就回收
例子:
public class SoftReferenceDemo {
/**
* 内存够用的时候
*/
public static void softRefMemoryEnough() {
// 创建一个强应用
Object o1 = new Object();
// 创建一个软引用
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1); // 输出:java.lang.Object@1b6d3586
System.out.println(softReference.get()); // 输出:java.lang.Object@1b6d3586
o1 = null;
// 手动GC
System.gc();
System.out.println(o1); // 输出:null
System.out.println(softReference.get()); // 输出:java.lang.Object@1b6d3586
}
/**
* JVM配置,故意产生大对象并配置小的内存,让它的内存不够用了导致OOM,看软引用的回收情况
* -Xms5m -Xmx5m -XX:+PrintGCDetails
*/
public static void softRefMemoryNoEnough() {
// 创建一个强应用
Object o1 = new Object();
// 创建一个软引用
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1); // 输出:java.lang.Object@1b6d3586
System.out.println(softReference.get()); // 输出:java.lang.Object@1b6d3586
o1 = null;
// 模拟OOM自动GC
try {
// 创建30M的大对象
byte[] bytes = new byte[30 * 1024 * 1024];
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(o1); // 输出:null
System.out.println(softReference.get()); // 输出:null
}
}
public static void main(String[] args) {
softRefMemoryEnough();
softRefMemoryNoEnough();
}
}
我们写了两个方法:一个是内存够用的时候,一个是内存不够用的时候
我们首先查看内存够用的时候,首先输出的是 o1
和软引用的softReference
,我们都能够看到值
然后我们把o1
设置为null
,执行手动GC
后,我们发现softReference
的值还存在,说明内存充足的时候,软引用的对象不会被回收:
java.lang.Object@1b6d3586
java.lang.Object@1b6d3586
null
java.lang.Object@1b6d3586
[GC (System.gc()) [PSYoungGen: 3932K->712K(76288K)] 3932K->720K(251392K), 0.0005912 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 712K->0K(76288K)] [ParOldGen: 8K->599K(175104K)] 720K->599K(251392K), [Metaspace: 3222K->3222K(1056768K)], 0.0033044 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 76288K, used 3277K [0x000000076b000000, 0x0000000770500000, 0x00000007c0000000)
eden space 65536K, 5% used [0x000000076b000000,0x000000076b333538,0x000000076f000000)
from space 10752K, 0% used [0x000000076f000000,0x000000076f000000,0x000000076fa80000)
to space 10752K, 0% used [0x000000076fa80000,0x000000076fa80000,0x0000000770500000)
ParOldGen total 175104K, used 599K [0x00000006c1000000, 0x00000006cbb00000, 0x000000076b000000)
object space 175104K, 0% used [0x00000006c1000000,0x00000006c1095ed0,0x00000006cbb00000)
Metaspace used 3229K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 349K, capacity 388K, committed 512K, reserved 1048576K
下面我们看当内存不够的时候,我们使用了JVM启动参数配置,给初始化堆内存为5M
-Xms5m -Xmx5m -XX:+PrintGCDetails
但是在创建对象的时候,我们创建了一个30M的大对象
// 创建30M的大对象
byte[] bytes = new byte[30 * 1024 * 1024];
这就必然会触发垃圾回收机制,这也是中间出现的垃圾回收过程,最后看结果我们发现,o1
和 softReference
都被回收了,因此说明:软引用在内存不足的时候,会自动回收
java.lang.Object@1b6d3586
java.lang.Object@1b6d3586
null
null # 较之前而言,此时softReference被回收了
[GC (Allocation Failure) [PSYoungGen: 1023K->488K(1536K)] 1023K->600K(5632K), 0.0007710 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1095K->488K(1536K)] 1207K->664K(5632K), 0.0005482 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 488K->488K(1536K)] 664K->700K(5632K), 0.0004621 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 488K->0K(1536K)] [ParOldGen: 212K->621K(4096K)] 700K->621K(5632K), [Metaspace: 3227K->3227K(1056768K)], 0.0033212 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 621K->621K(5632K), 0.0002788 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 621K->603K(4096K)] 621K->603K(5632K), [Metaspace: 3227K->3227K(1056768K)], 0.0035539 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 1536K, used 95K [0x00000000ffe00000, 0x0000000100000000, 0x0000000100000000)
eden space 1024K, 9% used [0x00000000ffe00000,0x00000000ffe17f78,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 4096K, used 603K [0x00000000ffa00000, 0x00000000ffe00000, 0x00000000ffe00000)
object space 4096K, 14% used [0x00000000ffa00000,0x00000000ffa96c90,0x00000000ffe00000)
Metaspace used 3259K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 353K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space # 出现异常
at SoftReferenceDemo.softRefMemoryNoEnough(SoftReferenceDemo.java:42)
at SoftReferenceDemo.main(SoftReferenceDemo.java:56)
弱引用
弱引用:不管内存是否够,只要有GC操作就会进行回收
弱引用需要用 java.lang.ref.WeakReference
类来实现,它比软引用生存期更短
对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,都会回收该对象占用的空间
例子:
public class WeakReferenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
WeakReference<Object> weakReference = new WeakReference<>(o1);
System.out.println(o1);
System.out.println(weakReference.get());
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(weakReference.get());
}
}
我们看结果,能够发现,我们并没有制造出OOM内存溢出,而只是调用了一下GC操作,垃圾回收就把它给收集了
java.lang.Object@14ae5a5
java.lang.Object@14ae5a5
[GC (System.gc()) [PSYoungGen: 5246K->808K(76288K)] 5246K->816K(251392K), 0.0008236 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 808K->0K(76288K)] [ParOldGen: 8K->675K(175104K)] 816K->675K(251392K), [Metaspace: 3494K->3494K(1056768K)], 0.0035953 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
null
null
软引用 & 弱引用
软引用和弱引用使用场景:
场景:假如有一个应用需要读取大量的本地图片
- 如果每次读取图片都从硬盘读取则会严重影响性能
- 如果一次性全部加载到内存中,又可能造成内存溢出
此时使用软引用可以解决这个问题
设计思路:使用 HashMap 来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占的空间,从而有效地避免了 OOM 的问题
Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
WeakHashMap 是什么?
比如一些常常和底层打交道的 mybatis 等,底层都应用到了 WeakHashMap
WeakHashMap 和 HashMap 类似,只不过它的 Key 是使用了弱引用的,也就是说,当执行 GC 的时候,HashMap 中的 key 会进行回收,下面我们使用例子来测试一下
我们使用了两个方法,一个是普通的 HashMap 方法
我们输入一个 Key-Value 键值对,然后让它的 key 置空,然后在查看结果
private static void myHashMap() {
Map<Integer, String> map = new HashMap<>();
Integer key = new Integer(1);
String value = "HashMap";
map.put(key, value);
System.out.println(map); // {1=HashMap}
key = null;
System.gc();
System.out.println(map); // {1=HashMap}
}
第二个是使用了WeakHashMap
,完整代码如下
private static void myWeakHashMap() {
Map<Integer, String> map = new WeakHashMap<>();
Integer key = new Integer(1);
String value = "WeakHashMap";
map.put(key, value);
System.out.println(map); // {1=WeakHashMap}
key = null;
System.gc();
System.out.println(map); // {}
}
从这里我们看到,对于普通的HashMap来说,key置空并不会影响,HashMap的键值对,因为这个属于强引用,不会被垃圾回收;
但是WeakHashMap,在进行GC操作后,弱引用的就会被回收
虚引用
虚引用又称为幽灵引用,需要java.lang.ref.PhantomReference
类来实现
顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。
如果一个对象持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列ReferenceQueue联合使用。
虚引用的主要作用是跟踪对象被垃圾回收的状态,仅仅是提供一种确保对象被 finalize 以后,做某些事情的机制。
PhantomReference
的get
方法总是返回null
,因此无法访问对象的引用对象。其意义在于说明一个对象已经进入 finalization 阶段,可以被 gc 回收,用来实现比 finalization 机制更灵活的回收操作
换句话说,设置虚引用关联唯一目的,就是在这个对象被收集器回收的时候,收到一个系统通知或者后续添加进一步的处理,Java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前,做必要的清理工作
这个就相当于Spring AOP里面的后置通知
场景:一般用于在回收时候做通知相关操作
ReferenceQueue:引用队列
软引用,弱引用,虚引用在回收之前,都可以用引用队列保存一下
我们在初始化的弱引用或者虚引用的时候,可以传入一个引用队列
Object o1 = new Object();
// 创建引用队列
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
// 创建一个弱引用,传入引用队列
WeakReference<Object> weakReference = new WeakReference<>(o1, referenceQueue);
那么在进行 GC 回收的时候,弱引用和虚引用的对象都会被回收,但是在回收之前,它会被送至引用队列中
完整代码如下:
public class PhantomReferenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
// 创建引用队列
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
// 创建一个弱引用
WeakReference<Object> weakReference = new WeakReference<>(o1, referenceQueue);
// 创建一个虚引用
PhantomReference<Object> phantomReference = new PhantomReference<>(o1, referenceQueue);
System.out.println(o1); // java.lang.Object@1b6d3586
System.out.println(weakReference.get()); // java.lang.Object@1b6d3586
System.out.println(phantomReference.get()); // null:虚引用的get方法总是返回null
// 取队列中的内容
System.out.println(referenceQueue.poll()); // null
System.out.println(referenceQueue.poll()); // null, 没有进行GC,队列中为空
o1 = null;
System.gc();
System.out.println("执行GC操作");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(o1); // null
System.out.println(weakReference.get()); // null
System.out.println(phantomReference.get()); // null
// 取队列中的内容
System.out.println(referenceQueue.poll()); // 发生了GC,引用队列中有内容了
System.out.println(referenceQueue.poll());
}
}
运行结果
java.lang.Object@1b6d3586
java.lang.Object@1b6d3586
null
null
null
执行GC操作
null
null
null
java.lang.ref.WeakReference@4554617c
java.lang.ref.PhantomReference@74a14482
从这里我们能看到,在进行垃圾回收后,我们弱引用对象,也被设置成null,但是在队列中还能够导出该引用的实例,这就说明在回收之前,该弱引用的实例被放置引用队列中了,我们可以通过引用队列进行一些后置操作
GCRoots和四大引用小总结
- 红色部分在垃圾回收之外,也就是强引用的
- 蓝色部分:属于软引用,在内存不够的时候,才回收
- 虚引用和弱引用:每次垃圾回收的时候,都会被干掉,但是它在干掉之前还会存在引用队列中,我们可以通过引用队列进行一些通知机制
JVM:Java中的引用的更多相关文章
- Java中弱引用、软引用、虚引用及强引用的区别
Java中弱引用VS软引用 Java中有如下四种类型的引用: 强引用(Strong Reference) 弱引用(WeakReference) 软引用(SoftReference) 虚引用(Phant ...
- Java中的引用传递和值传递
Java中的引用传递和值传递 关于Java的引用传递和值传递,在听了老师讲解后,还是没有弄清楚是怎么一回事,于是查了资料,所以在这里与大家分享,有不对的地方,欢迎大家留言. java中是没有指针的,j ...
- Java中没有引用传递只有值传递(在函数中)
◆传参的问题 引用类型(在函数调用中)的传参问题,是一个相当扯的问题.有些书上说是传值,有些书上说是传引用.搞得Java程序员都快成神经分裂了.所以,我们最后来谈一下“引用类型参数传递”的问题. 如下 ...
- Java中的引用与ThreadLocal
Java中的引用--强软弱虚 强引用 Object object = new Object(),这个object就是一个强引用.如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回 ...
- 浅谈Java中的引用
在Java语言中,引用是指,某一个数据,代表的是另外一块内存的的起始地址,那么我们就称这个数据为引用. 在JVM中,GC回收的大致准则,是认定如果不能从根节点,根据引用的不断传递,最终指向到一块内存区 ...
- JAVA中的引用
关于值类型和引用类型的话题,C++.JAVA.python.go.C#等等高级语言都有相关的概念,只要理解了其底层工作原理,可以说即使是不同的语言,在面试学习工作实践中都可以信手拈来(不要太纠集语言) ...
- Java中各种引用(Reference)解析
目录 1,引用类型 2, FinalReference 2.1, Finalizer 3, SoftReference 4, WeakReference 5, PhantomReference 6, ...
- 探究Java中的引用
探究Java中的四种引用 从JDK1.2版本开始,Java把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期.这四种级别由高到低依次为:强引用.软引用.弱引用和虚引用.本篇就来详细探究 ...
- 理解Java中的引用传递和值传递
关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题,有论坛说Java中只有值传递,也有些地方说引用传递和值传递都存在,比较容易让人迷惑.关于值传递和引用传递其实需要分情况看待,今天学习 ...
随机推荐
- Raid(0/1/5/10)
一.Raid需要的硬盘数量 1.raid 0: 最少1块硬盘(但是1块盘没有意义,至少2块才有实际意义) 2.raid 1: 最少2块硬盘 3.raid 5: 最少3块硬盘 4 ...
- [考试总结]noip模拟45
真开心,挂没了.. 考完:"你们怎么第二题打了这么点分,明明一个爆搜就有65pts!!!怎么跟别人打?!" 然后我看了看我的爆搜,30pts. 然后认为自己打爆了... 我又想为什 ...
- Spring Cloud Eureka 实践(一)
Spring Cloud Eureka是Spring Cloud Netflix微服务套件中的一部分,主要在Spring Cloud架构中提供服务注册发现的功能.那么是不是可以尝试在本地搭一个单例Eu ...
- Python常见问题 - 写入数据到 excel 报 ValueError: invalid literal for int() with base 10 错误
背景 在上写入数据到excel中,报了以下错误 出现原因 对于写入excel场景下出现该错误的话,很大概率是写入数据的单元格原本的数据格式有问题 解决方法 清理掉单元格的旧数据,然后再写入就可以了
- Docker(41)- Portainer 可视化面板安装
Portainer docker run -d -p 8080:9000 \ --restart=always -v /var/run/docker.sock:/var/run/docker.sock ...
- system、 exec函数族、fork函数用法说明
system(), exec函数族, fork函数用法说明 启动一个新线程的方式: system() 该函数经常用来在C程序中调用shell脚本或者命令行程序. 特点: 效率低下,首先需要创建一个sh ...
- 从零开始学习SQL SERVER(1)--- 了解SQL
SQL是什么 SQL (发音为 sequal [' sikwəl ' ]) SQL指 Structured Query Language 结构化查询语言,是用于访问和处理数据库的标准的计算机语言. ...
- RabbitMQie消息列队整理
使用方法过程,这儿只做了windows平台教程 先安装Erlang 编程软件,然后设置环境变量,在安装RabbimMQ ,这儿我下载了一个版本不行,后来换了最新版就好了,以后在使用过程 中如果有问题 ...
- 谈谈如何进阶Java高级工程师
从入门到瓶颈(++文末附学习脑图++) 首先,先自我介绍一下,楼主94年的,四川人,普通大专毕业. 第一阶段 实习阶段 2015年,实习阶段去浙江温州(没错,就是皮革厂的那个地方)找了份软件实施的工作 ...
- HDU 6170 Two strings( DP+字符串匹配)
http://acm.hdu.edu.cn/showproblem.php?pid=6170 题目大意: 给出两个字符串s1和s2(长度小于等于2500). s1是一个正常的包含大小写字母的字符串,s ...