深入JVM对象引用
在jdk 1.2以前,创建的对象只有处在可触及(reachaable)状态下,才能被程序所以使用,垃圾回收器一旦发现无用对象,便会对其进行回收。但是,在某些情况下,我们希望有些对象不需要立刻回收或者说从全局的角度来说并没有立刻回收的必要性。比如缓存系统的设计,在内存不吃紧或者说为了提高运行效率的情况下,一些暂时不用的对象仍然可放置在内存中,而不是立刻进行回收。因此,从jdk 1.2 版本开始,java设计人员把对象的引用细分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)四种级别,主要区别体现在在被GC回收的优先级上:强引用->软引用->弱引用->虚引用。也就是说从jdk 1.2开始,垃圾回收器回收对象时,对象的可达性分析需要考虑考虑对象的引用强度,也就是说现在对象的有效性=可达性+引用类型。先来看下类层次结构如下:
1. 引用的四种类型
1. 强引用(Strong Reference)
在代码中普遍使用的,类似Person person=new Person();如果一个对象具有强引用,则无论在什么情况下,GC都不会回收被引用的对象。当内存空间不足时,JAVA虚拟机宁可抛出OutOfMemoryError终止应用程序也不会回收具有强引用的对象。
2. 软引用(Soft Reference)
表示一个对象处在有用但非必须的状态。如果一个对象具有软引用,在内存空间充足时,GC就不会回收该对象;当内存空间不足时,GC会回收该对象的内存(回收发生在OutOfMemoryError之前)。
Person person=new Person();
SoftReference sr=new SoftReference(person);
1
2
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被GC回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中,以便在恰当的时候将该软引用回收。但是由于GC线程的优先级较低,通常手动调用System.gc()并不能立即执行GC,因此弱引用所引用的对象并不一定会被马上回收。
3. 弱引用(Weak Reference)
用来描述非必须的对象。它类似软引用,但是强度比软引用更弱一些:弱引用具有更短的生命.GC在扫描的过程中,一旦发现只具有被弱引用关联的对象,都会回收掉被弱引用关联的对象。换言之,无论当前内存是否紧缺,GC都将回收被弱引用关联的对象。
Person person=new Person();
WeakReference wr=new WeakReference(person);
1
2
同样弱引用也可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被GC回收了,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中,以便在恰当的时候将该弱引用回收。
4. 虚引用(Phantom Reference)
虚引等同于没有引用,这意味着在任何时候都可能被GC回收,设置虚引用的目的是为了被虚引用关联的对象在被垃圾回收器回收时,能够收到一个系统通知。(被用来跟踪对象被GC回收的活动)虚引用和弱引用的区别在于:虚引用在使用时必须和引用队列(ReferenceQueue)联合使用,其在GC回收期间的活动如下:
ReferenceQueue queue=new ReferenceQueue();
PhantomReference pr=new PhantomReference(object.queue);
1
2
也即是GC在回收一个对象时,如果发现该对象具有虚引用,那么在回收之前会首先该对象的虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入虚引用来了解被引用的对象是否被GC回收。
ReferenceQueue和Reference
1. ReferenceQueue含义及作用
通常我们将其ReferenceQueue翻译为引用队列,换言之就是存放引用的队列,保存的是Reference对象。其作用在于Reference对象所引用的对象被GC回收时,该Reference对象将会被加入引用队列中(ReferenceQueue)的队列末尾,这相当于是一种通知机制.当关联的引用队列中有数据的时候,意味着引用指向的堆内存中的对象被回收。通过这种方式,JVM允许我们在对象被销毁后,做一些我们自己想做的事情。JVM提供了一个ReferenceHandler线程,将引用加入到注册的引用队列中
ReferenceQueue常用的方法:
public Reference<? extends T> poll():从队列中取出一个元素,队列为空则返回null;
public Reference<? extends T> remove():从队列中出对一个元素,若没有则阻塞至有可出队元素;
public Reference<? extends T> remove(long timeout):从队列中出对一个元素,若没有则阻塞至有可出对元素或阻塞至超过timeout毫秒;
见如下代码:
ReferenceQueue< Person> rq=new ReferenceQueue<Person>();
Person person=new Person();
SoftReference sr=new SoftReference(person,rq);
1
2
3
这段代码中,对于Person对象有两种引用类型,一是person的强引用,而是sr的软引用。sr强引用了SoftReference对象,该对象软引用了Person对象。当person被回收时,sr所强引用的对象将会被放到rq的队列末尾。利用ReferenceQueue可以清除失去了软引用对象的SoftReference,如下操作:
SoftReference ref=null;
while((ref=(Person)rq.poll())!=null){
//清除
}
1
2
3
4
5
2. Reference类
Reference是SoftReference,WeakReference,PhantomReference类的父类,其内部通过一个next字段来构建了一个Reference类型的单向列表,而queue字段存放了引用对象对应的引用队列,若在Reference的子类构造函数中没有指定,则默认关联一个ReferenceQueue.NULL队列。
3. 四种引用类型使用场景
强引用类型是在代码中普遍存在,无须解释太多了
软引用和弱引用:两者都可以实现缓存功能,但软引用实现的缓存通常用在服务端,而在移动设备中的内存更为紧缺,对垃圾回收更为敏感,因此android中的缓存通常是用弱引用来实现(比如LruCache)
在开发中有这么一个场景:用户信息查询。在不考虑对用户信息更改的情况下,通常有以下两种方案来实现:
1. 每次查询时,连接数据库获取信息,缺点是IO读频繁,平均响应时间较长。优点是内存占用少。
2. 第一次查询时,读取数据库后将用户信息存放在内存中,以后每次查询从内存中读取,优点是读取速度快,在请求次数较少的情况下,内存占用较多。
现在我们用采用第二种方案,基于缓存来设计,代码如下:
User.java
public class User {
private String id;
private String name;
private int age;
public User(String id) {
super();
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
}
UserCache.java
public class UserCache {
private static UserCache cache;
private Hashtable<String, UserRef> userRefs;
private ReferenceQueue<User> q;
private class UserRef extends SoftReference<User>{
private String key="";
public UserRef(User user,ReferenceQueue<User> q){
super(user,q);
key=user.getId();
}
}
private UserCache(){
userRefs=new Hashtable<>();
q=new ReferenceQueue<>();
}
public static UserCache getInstance(){
synchronized (UserCache.class) {
if(cache==null){
synchronized (UserCache.class) {
cache=new UserCache();
}
}
}
return cache;
}
/**
* 缓存用户数据
* @param user
*/
private void cacheObject(User user){
cleanCache();
UserRef ref = new UserRef(user, q);
userRefs.put(user.getId(), ref);
}
/**
* 获取用户数据
* @param id
* @return
*/
public User getObject(String id){
User user=null;
if(userRefs.containsKey(id)){
System.err.println("get data from cache");
UserRef ref = userRefs.get(id);
user=ref.get();
}
if(user==null){
System.err.println("get data from db");
user=new User(id);
user.setName("dong"+new Random().nextInt(10));
cacheObject(user);
}
return user;
}
private void cleanCache() {
UserRef ref=null;
while((ref=(UserRef) q.poll())!=null){
userRefs.remove(ref.key);
}
}
public void clearAll(){
cleanCache();
userRefs.clear();
System.gc();
System.runFinalization();
}
}
UserCacheTest.java
public class UserCacheTest {
public static void main(String[] args) {
UserCache cache = UserCache.getInstance();
for(int i=0;i<2;i++){
System.out.println(cache.getObject("123").toString());
}
cache.clearAll();
System.out.println(cache.getObject("123").toString());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
这样简单的缓存就完成了,输出结果如下:
get data from db
get data from cache
User [id=123, name=dong3, age=0]
User [id=123, name=dong3, age=0]
get data from db
User [id=123, name=dong2, age=0]
4. 到底是什么引用类型?
如果一个对象有多个引用类型,那在进行垃圾回收时如何判断对象的可达性呢?其原则如下:
单挑引用链的可达性以最弱的一个引用类型来决定;
多条引用链的可达性以最强的一个引用类型来决定;
举例说明,如下图所示:
对Object 2进行分析,路径1-1——>2-1中取最弱的引用,软引用;路径1-2——>2-2中取最弱引用,虚引用;在这两条路径中取最强引用,软引用。因此Object 2是最终的引用类型是软引用。
示例代码
我们通过一段代码帮助理解这四种:
private void test_gc1(){
//在heap中创建内容为"wohenhao"的对象,并建立a到该对象的强引用,此时该对象时强可及
String a=new String("wohenhao");
//对heap中的对象建立软引用,此时heap中的对象仍然是强可及
SoftReference< ?> softReference=new SoftReference<String>(a);
//对heap中的对象建立弱引用,此时heap中的对象仍然是强可及
WeakReference< ?> weakReference=new WeakReference<String>(a);
System.out.println("强引用:"+a+"\n软引用"+softReference.get()+"\n弱引用"+weakReference.get()+"\n");
//heap中的对象从强可及到软可及
a=null;
System.out.println("强引用:"+a+"\n软引用"+softReference.get()+"\n弱引用"+weakReference.get()+"\n");
softReference.clear();//heap中对象从软可及变成弱可及,此时调用System.gc(),
System.gc();
System.out.println("强引用:"+a+"\n软引用"+softReference.get()+"\n弱引用"+weakReference.get()+"\n");
}
private void test_gc2(){
//在heap中创建内容为"wohenhao"的对象,并建立a到该对象的强引用,此时该对象时强可及
String a=new String("wohenhao");
//对heap中的对象建立软引用,此时heap中的对象仍然是强可及
SoftReference< ?> softReference=new SoftReference<String>(a);
//对heap中的对象建立弱引用,此时heap中的对象仍然是强可及
WeakReference< ?> weakReference=new WeakReference<String>(a);
System.out.println("强引用:"+a+"\n软引用"+softReference.get()+"\n弱引用"+weakReference.get()+"\n");
a=null;//heap中的对象从强可及到软可及
System.out.println("强引用:"+a+"\n软引用"+softReference.get()+"\n弱引用"+weakReference.get()+"\n");
System.gc();
System.out.println("强引用:"+a+"\n软引用"+softReference.get()+"\n弱引用"+weakReference.get()+"\n");
}
深入JVM对象引用的更多相关文章
- 一夜搞懂 | JVM GC&内存分配
前言 本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍: 我的GIthub博客 学习导图 一.为什么要学习GC&内存分配? 时代发展到现在,如今的内存动态分配与内存回收技术已经相当成 ...
- Java对象引用/JVM分级引用——强引用、软引用、弱引用、虚引用
无论是通过引用计数法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判断对象是否存活都与“引用”有关, 相关资料:如何判断对象是否存活/死去 那么引用究竟是什么?让我们一起来看一下 ...
- JVM虚拟机结构
JVM的主要结构如下图所示,图片引用自舒の随想日记. 方法区和堆由所有线程共享,其他区域都是线程私有的 程序计数器(Program Counter Register) 类似于PC寄存器,是一块较小的内 ...
- JVM学习(4)——全面总结Java的GC算法和回收机制
俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 一些JVM的跟踪参数的设置 Java堆的分配参数 -Xmx 和 –Xms 应该保持一个什么关系,可以让系统的 ...
- JVM学习(2)——技术文章里常说的堆,栈,堆栈到底是什么,从os的角度总结
俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 堆栈是栈 JVM栈和本地方法栈划分 Java中的堆,栈和c/c++中的堆,栈 数据结构层面的堆,栈 os层面 ...
- JVM学习(1)——通过实例总结Java虚拟机的运行机制
俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: JVM的历史 JVM的运行流程简介 JVM的组成(基于 Java 7) JVM调优参数:-Xmx和-Xms ...
- jvm系列(四):jvm调优-命令大全(jps jstat jmap jhat jstack jinfo)
文章同步发布于github博客地址,阅读效果更佳,欢迎品尝 运用jvm自带的命令可以方便的在生产监控和打印堆栈的日志信息帮忙我们来定位问题!虽然jvm调优成熟的工具已经有很多:jconsole.大名鼎 ...
- jvm系列(二):JVM内存结构
JVM内存结构 所有的Java开发人员可能会遇到这样的困惑?我该为堆内存设置多大空间呢?OutOfMemoryError的异常到底涉及到运行时数据的哪块区域?该怎么解决呢?其实如果你经常解决服务器性能 ...
- jvm系列(一):java类的加载机制
java类的加载机制 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装 ...
随机推荐
- 前端页面报net::ERR_CONNECTION_RESET错误的原因
本机和测试环境都是OK的.但是一到线上就报错:可能原因总结如下: 1 可能是服务器限制了文件上传的权限. 解决方法:开通了文件上传权限. 2 也许导致这种错误的方式有很多,可能是因为post请求时提交 ...
- Spring数据库开发
Spring的数据库开发 #Spring中JDBC模板的作用 JDBC模板负责数据库资源管理和错误处理: #熟悉Spring JDBC的配置 配置数据源和jdbc模板 <?xml versio ...
- 笔面试复习(spring常用.jar包/事务/控制反转/bean对象管理和创建/springMVC工作原理/sql查询)
###spring常用jar包1.spring.jar是包含有完整发布模块的单个jar包.2.org.springframework.aop包含在应用中使用Spring的AOP特性时所需要的类.3.o ...
- springMVC之一(页面<--->控制器 互相传值,转发和重定向)
#页面--->控制器1.request:不建议使用2.使用属性传值(建议使用)@RequestParam("name") String username3.使用Bean对象传 ...
- python分割txt文件
a=open('A.txt','r').readlines() n=3 #份数 qty=len(a)//n if len(a)%n==0 else len(a)//n+1 #每一份的行数 for i ...
- K8S学习笔记之将Google的gcr.io、k8s.gcr.io 换为国内镜像
0x00 添加docker官方的国内镜像 sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ...
- php 版本号 整数化 mysql存储入库 比较大小【版本处理类,提供版本与数字互相转换】
下载地址:https://download.csdn.net/download/a724008158/10659015
- fjwc2019 D1T2 原样输出(后缀自动机+dp)
#179. 「2019冬令营提高组」原样输出 暴力对每个串建后缀自动机,然后暴力枚举每个自动机的子串.可以拿到部分分. 然鹅我们可以把每个后缀自动机连起来. 我们知道,后缀自动机是用最少的点(空间)表 ...
- Python3 Iterator and Generator
Python3 Iterator and Generator iterator 主要是利用 iter 函数 >>> list=[1,2,3,4] >>> it = ...
- 批处理:根据进程名称查询进程,如果有进程就输出up没有就输出donw
需求:windows系统上 根据进程名称查询进程,如果有进程就输出 up ,没有就输出 donw. ::Final interpretation is owned by chenglee ::@e ...