在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对象引用的更多相关文章

  1. 一夜搞懂 | JVM GC&内存分配

    前言 本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍: 我的GIthub博客 学习导图 一.为什么要学习GC&内存分配? 时代发展到现在,如今的内存动态分配与内存回收技术已经相当成 ...

  2. Java对象引用/JVM分级引用——强引用、软引用、弱引用、虚引用

    无论是通过引用计数法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判断对象是否存活都与“引用”有关, 相关资料:如何判断对象是否存活/死去 那么引用究竟是什么?让我们一起来看一下 ...

  3. JVM虚拟机结构

    JVM的主要结构如下图所示,图片引用自舒の随想日记. 方法区和堆由所有线程共享,其他区域都是线程私有的 程序计数器(Program Counter Register) 类似于PC寄存器,是一块较小的内 ...

  4. JVM学习(4)——全面总结Java的GC算法和回收机制

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 一些JVM的跟踪参数的设置 Java堆的分配参数 -Xmx 和 –Xms 应该保持一个什么关系,可以让系统的 ...

  5. JVM学习(2)——技术文章里常说的堆,栈,堆栈到底是什么,从os的角度总结

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 堆栈是栈 JVM栈和本地方法栈划分 Java中的堆,栈和c/c++中的堆,栈 数据结构层面的堆,栈 os层面 ...

  6. JVM学习(1)——通过实例总结Java虚拟机的运行机制

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: JVM的历史 JVM的运行流程简介 JVM的组成(基于 Java 7) JVM调优参数:-Xmx和-Xms ...

  7. jvm系列(四):jvm调优-命令大全(jps jstat jmap jhat jstack jinfo)

    文章同步发布于github博客地址,阅读效果更佳,欢迎品尝 运用jvm自带的命令可以方便的在生产监控和打印堆栈的日志信息帮忙我们来定位问题!虽然jvm调优成熟的工具已经有很多:jconsole.大名鼎 ...

  8. jvm系列(二):JVM内存结构

    JVM内存结构 所有的Java开发人员可能会遇到这样的困惑?我该为堆内存设置多大空间呢?OutOfMemoryError的异常到底涉及到运行时数据的哪块区域?该怎么解决呢?其实如果你经常解决服务器性能 ...

  9. jvm系列(一):java类的加载机制

    java类的加载机制 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装 ...

随机推荐

  1. Redis Windows 安装

    摘自:https://www.cnblogs.com/M-LittleBird/p/5902850.html 一.下载windows版本的Redis 去官网找了很久,发现原来在官网上可以下载的wind ...

  2. Spring MVC数据绑定

    1.绑定默认数据类型 当前端请求参数较为简单的时候,后台形参可以直接使用SpringMVC提供的参数类型来绑定数据. HttpServletRequest:通过request对象获取请求信息: Htt ...

  3. Docker学习笔记之Copy on Write机制

    0x00 概述 Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新 ...

  4. Netty1

    基于Netty4的HttpServer和HttpClient的简单实现 Netty的主页:http://netty.io/index.html 使用的Netty的版本:netty-4.0.23.Fin ...

  5. protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - unknown delivery tag 2, class-id=60, method-id=80)

    Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; reason: {#method<channel.c ...

  6. Codeforces Round #483 (Div. 2)题解

    A. Game time limit per test 2 seconds memory limit per test 256 megabytes input standard input outpu ...

  7. django基础 -- 9.中间件

    一.中间件的介绍 中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级, 并且在全局上改变django的输入与输出.因为改变的是全局,所以需要谨慎实用, 用不好 ...

  8. Python3基础 iter+next 进行迭代时超出了范围 产生StopIteration异常

             Python : 3.7.0          OS : Ubuntu 18.04.1 LTS         IDE : PyCharm 2018.2.4       Conda ...

  9. docker版本升级

    docker的版本变化: Docker从1.13.x版本开始,版本分为企业版EE和社区版CE,版本号也改为按照时间线来发布,比如17.03就是2017年3月,有点类似于ubuntu的版本发布方式. 企 ...

  10. SpringBoot 读取properties配置文件 @Value使用 中文乱码问题

    一,idea中配置文件中文乱码问题 使用idea开发,读取properites配置文件 配置: #app 菜单 #没有限制,所有人都可访问的菜单 menu.unlimited=订单审批,现场尽调,合作 ...