知识点的梳理:

  • 可触及性包含3种状态:对象只有在不可触及状态时才会被回收!;
    • 可触及的:从根节点开始,可以到达这个对象;
    • 可复活的:对象的所有引用都被释放,但是对象有可能在finalize()函数中复活;
    • 不可触及的:对象的finalize()函数被调用,并且没有复活,就会进入不可触及状态。该状态的对象不可能被复活,因为finalize()函数只会被调用一次;
  • Java提供4个级别的引用:强引用,软引用,弱引用,虚引用;
  • 说明
    • 可触及性的必要性:不被使用的对象,很有可能借助finalize()函数复活自己,所以我们需要一个状态来判定一个对象到底是否可回收!
    • 示例1:对象的复活

public class CanReliveObj {

public static CanReliveObj obj;

@Override

protected void finalize() throws Throwable {

super.finalize();

System.out.println("CanReliveObj finalize called");

obj = this;

}

@Override

public String toString() {

return "I am CanReliveObj";

}

public static void main(String[] args) throws InterruptedException {

obj = new CanReliveObj();

//第一次清除对象,对象通过finalize()复活

obj = null;

System.gc();

Thread.sleep(1000);

if(obj == null){

System.out.println("obj是null");

}else{

System.out.println("obj 可用");

}

System.out.println("第2次GC");

//第二次清除对象,无法再复活

obj = null;

System.gc();

Thread.sleep(1000);

if(obj == null){

System.out.println("obj是null");

}else{

System.out.println("obj 可用");

}

}

}

  • 引用和可触及性的强度
    • 软引用,弱引用,虚引用可在java.lang.ref包中找到它们。FinalReference表示"最终"引用,它用于实现对象的finalize()方法
    • 强引用
      • 能力:程序中一般的引用类型,此级别的对象是可触及的,不会被回收;
        • 能力特点:
          • 可直接访问目标对象;
          • 强引用指向的对象在任何时候都不会被回收,虚拟机宁愿抛出OOM异常,也不会回收强引用所指向的对象;
          • 强引用可能导致内存泄漏;
      • 示例:

步骤1 : StringBuffer str = new StringBuffer("Hello World");

说明:假设此段代码在函数体内运行,那么局部变量str将被分配在栈上,而对象StringBuffer实例被分配在堆上。局部变量str指向StringBuffer实例所在堆空间,通过str可以操作该实例,那么str就是StringBuffer实例的强引用

步骤2:StringBuilder str1 = str;

此时str1指向str指向的对象,同时在局部变量表上会分配空间存放str1变量。此时,该StringBuffer示例就有两个引用。对引用的"=="操作用于表示两操作数所指向的堆空间地址是否相同,不表示两操作数所指向的对象是否相等

这两个引用都是强引用

  • 软引用--- 可被回收的引用
    • 能力:比强引用弱一点的,软可触及的引用类型;
      • 能力特点:
        • 堆空间不足时,会被回收;
        • 使用java.lang.SoftReference类实现;
    • 示例1:软引用在系统堆内存不足时被回收

public class SoftRef {

//声明User类

public static class User {

public User(int id,String name){

this.id = id;

this.name = name;

}

public int id;

public String name;

@Override

public String toString() {

return "User [id=" + id + ", name=" + name + "]";

}

}

public static void main(String[] args) {

User u = new User(1,"geym");//建立user实例,强引用

SoftReference<User> userSoftRef = new SoftReference<SoftRef.User>(u);//通过强引用,建立软引用

u = null;//去除强引用

System.out.println(userSoftRef.get());//从软引用获取强引用对象

System.gc();//垃圾回收

System.out.println("After GC:");

System.out.println(userSoftRef.get());//再次获取软引用中的对象

byte[] b =new byte[1024*925*7];//分配一块大内存,让系统认为内存紧张

System.gc();//垃圾回收

System.out.println(userSoftRef.get());//从软引用获取数据

}

}

运行参数:-Xmx10m
运行效果:

User [id=1, name=geym](第一次从软引用中获取数据)

After GC:

User [id=1, name=geym](GC没有清除软引用)
null(由于内存紧张,软引用被清除)

  • 示例2:每个软引用都可以附带一个引用队列,当对象的可达性状态发生改变时(由可达变为不可达),软引用对象就会进入引用队列。通过该队列,可跟踪对象的回收情况

public class SoftRefQ {

public static class User{

public User(int id,String name){

this.id = id;

this.name = name;

}

public int id;

public String name;

@Override

public String toString() {

return "User [id=" + id + ", name=" + name + "]";

}

}

static ReferenceQueue<User> softQueue = null;

public static class CheckRefQueue extends Thread{

@Override

public void run() {

//跟踪引用队列

while(true){

if(softQueue != null){

UserSoftReference obj = null;

try{

obj = (UserSoftReference)softQueue.remove();

}catch(Exception e){

e.printStackTrace();

}

if(obj != null){

System.out.println("user id" + obj.uid+" is delete");

}

}

}

}

}

//实现自定义软引用类,扩展软引用的目的是记录User.uid,在后续引用队列中,可以通过这个uid知道哪个User实例被回收了

public static class UserSoftReference extends SoftReference<User>{

int uid;

public UserSoftReference(User referent,ReferenceQueue<? super User> q) {

super(referent,q);

uid=referent.id;

}

}

public static void main(String[] args) throws InterruptedException {

Thread t = new CheckRefQueue();

t.setDaemon(true);

t.start();

User u = new User(1,"geym");//建立user实例,强引用

softQueue = new ReferenceQueue<User>();//通过强引用,建立软引用

//创建软引用时,指定了一个软引用队列,当给定的对象实例被回收时,就会被加入这个引用队列,通过访问该队列可以跟踪对象的回收情况

UserSoftReference userSoftRef = new UserSoftReference(u,softQueue);

u = null;//去除强引用

System.out.println(userSoftRef.get());//从软引用获取强引用对象

System.gc();//垃圾回收

//内存足够,不会被回收

System.out.println("After GC:");

System.out.println(userSoftRef.get());//再次获取软引用中的对象

System.out.println("try to create byte array and GC");

byte[] b =new byte[1024*925*7];//分配一块大内存,让系统认为内存紧张

System.gc();//垃圾回收

System.out.println(userSoftRef.get());//从软引用获取数据

Thread.sleep(1000);

}

}

使用如下参数执行此段代码:-Xmx10m
运行结果:

User [id=1, name=geym](第一次从软引用获得对象)

After GC:

User [id=1, name=geym](GC后,软引用对象没有回收)

try to create byte array and GC(创建大数组,耗尽内存)

user id1 is delete(引用队列探测到对象被删除)

null(对象已被回收,无法再通过软引用获取对象)

  • 弱引用---发现即回收
    • 能力:弱可触及的引用类型。比软引用稍弱。只要发现弱引用,不管系统堆空间使用情况如何,都会将对象进行回收。
      • 能力特点:
        • 垃圾回收器线程优先级很低,因此,并不一定能很快发现持有弱引用的对象。这种情况下,弱引用对象可以存在较长的时间;
        • 一旦一个弱引用对象被垃圾回收器回收,便会加入到一个注册的引用队列中;(这一点和软引用一样,可查看软引用的代码示例)
        • 使用java.lang.ref.WeakReference类实现
      • 能力分析:
        • 软引用,弱引用都适合来保存那些可有可无的缓存数据。这样做,可以让系统内存不足时,让这些缓存数据被回收,不会导致内存溢出。而当内存资源充足时,这些缓存数据又可以存在相当长的时间,从而起到加速系统的作用;
    • 示例1:

public class WeakRef {

public static class User{

public User(int id,String name){

this.id = id;

this.name = name;

}

public int id;

public String name;

@Override

public String toString() {

return "User [id=" + id + ", name=" + name + "]";

}

}

public static void main(String[] args) {

User u = new User(1,"hey");

//构造弱引用

WeakReference<User> userWeakRef = new WeakReference<User>(u);

u = null;//去除强引用

System.out.println(userWeakRef.get());//从弱引用中重新获取对象

System.gc();//GC

//不管当前内存空间足够与否,都会回收它的内存

System.out.println("After GC:");

System.out.println(userWeakRef.get());//重新尝试从弱引用中获取对象

}

}

运行结果:

User [id=1, name=hey]

After GC:

null

  • 虚引用--- 对象回收跟踪
    • 能力:虚可触及的引用了类型。所有应用类型中最弱的一个。持有虚引用的对象,和没有引用几乎是一样的。随时都可能被垃圾回收器回收。
      • 能力特点:
        • 试图通过虚引用的get()方法取得强引用时,总是会失败;
        • 虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程;
        • 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象后,将这个虚引用加入引用队列,已通知应用程序对象的回收情况;
    • 示例1:

public class TraceCanReliveObj {

public static TraceCanReliveObj obj;

static ReferenceQueue<TraceCanReliveObj> phantomQueue = null;

public static class CheckRefQueue extends Thread{

@Override

public void run() {

//跟踪引用队列

while(true){

if(phantomQueue != null){

PhantomReference<TraceCanReliveObj> objt = null;

try{

objt = (PhantomReference<TraceCanReliveObj>)phantomQueue.remove();

}catch(Exception e){

e.printStackTrace();

}

if(objt != null){

System.out.println("TraceCanReliveObj is delete by GC");

}

}

}

}

}

@Override

protected void finalize() throws Throwable {

super.finalize();

System.out.println("CanReliveObj finalize called");

obj = this;

}

@Override

public String toString() {

return "I am CanReliveObj";

}

public static void main(String[] args) throws InterruptedException {

Thread t = new CheckRefQueue();

t.setDaemon(true);

t.start();

phantomQueue = new ReferenceQueue<TraceCanReliveObj>();

obj = new TraceCanReliveObj();

PhantomReference<TraceCanReliveObj> phantomRef = new PhantomReference<TraceCanReliveObj>(obj,phantomQueue);

obj = null;

System.gc();

Thread.sleep(1000);

if(obj==null){

System.out.println("obj是null");

}else{

System.out.println("obj可用");

}

System.out.println("第2次GC");

obj = null;

System.gc();

Thread.sleep(1000);

if(obj == null){

System.out.println("obj是null");

}else{

System.out.println("obj可用");

}

}

}

执行效果:

CanReliveObj finalize called(对象复活)

obj可用

第2次GC(第2次,对象无法复活)

TraceCanReliveObj is delete by GC(引用队列捕获到对象被回收)

obj是null

Java虚拟机--判断可触及性(七)相关推荐

  1. java中对象的生存期_深入理解Java虚拟机-判断对象是否存活算法与对象引用

    我们知道Java中的对象一般存放在堆中,但是总不能让这些对象一直占着内存空间,这些对象最终都会被回收并释放内存,那么我们如何判断对象已经成为垃圾呢?这篇文章会提出两种算法解决这个问题.另外,本文还要谈 ...

  2. Java 虚拟机 最易理解的 全面解析

    先上一个最容易理解的类实例化的内存模型案例截图: 转载自:https://www.zybuluo.com/Yano/note/321063 周志明著的<深入理解 Java 虚拟机>的干货~ ...

  3. 《深入理解 Java 虚拟机》转载周志明

    JDK 是什么? JRE 是什么? Java历史版本的特性? Java Version SE 5.0 Java Version SE 6 Java Version SE 7 Java 8 运行时数据区 ...

  4. 《深入理解Java虚拟机》-周志明(转)

    (转载):如果文章有错误,欢迎评论或私信指出,谢谢~ https://blog.csdn.net/Yano_nankai/article/details/50957578 本文固定链接:https:/ ...

  5. JAVA虚拟机:JVM和Java体系架构

    一.Java及JVM简介 2022 TIOBE 排行榜: https://www.tiobe.com/tiobe-index/ 二.Java 生态圈 Java 是目前应用最为广泛的软件开发平台之一.拥 ...

  6. 【java虚拟机系列】JVM类加载器与ClassNotFoundException和NoClassDefFoundError

    在我们日常的项目开发中,会经常碰到ClassNotFoundException和NoClassDefFoundError这两种异常,对于经验足够的工程师而言,可能很轻松的就可以解决,但是却不一定明白为 ...

  7. 深入浅出解读 Java 虚拟机的差别测试技术

    为什么80%的码农都做不了架构师?>>>    本文分享基于字节码种子生成有效.可执行的字节码文件变种,并用于 JVM 实现的差别测试.本文特别提出用于修改字节码语法的classfu ...

  8. 虚拟机与Java虚拟机

    虚拟机 所谓虚拟机(Virtual Machine),就是一台虚拟的计算机.它是一款软件,用来执行一系列虚拟计算机指令.大体上,虚拟机可以分为系统虚拟机和程序虚拟机. 大名鼎鼎的Visual Box, ...

  9. 【jvm系列-01】初识虚拟机与java虚拟机

    JVM系列整体栏目 内容 链接地址 [一]初识虚拟机与java虚拟机 https://blog.csdn.net/zhenghuishengq/article/details/129544460 [二 ...

最新文章

  1. 软件测试_单元测试反模式,完整列表
  2. add_compile_options和CMAKE_CXX_FLAGS的区别
  3. Eclipse公共许可证
  4. Winform开发中另一种样式的OutLookBar工具条
  5. Java多线程(review)
  6. java邮箱地址正则表达式_Java 中用正则表达式修改 Email 地址
  7. IT互联网公司的笔试的输入输出- c++ python
  8. 消除SQL Server中重要维护任务的风险
  9. wget 下载需要登录验证的网页文件
  10. 用Python实现视频字符化(蔡徐坤唱跳Rap视频)
  11. 怎样下载苹果 Apple Store 官方 APP 里的限时免费应用福利?
  12. web服务之LAMPLNMP架构
  13. 程序异常终止:Process finished with exit code -1073741819 (0xC0000005)
  14. 微软 Surface Laptop 系统恢复
  15. 阿里云 mysql 1045_解决阿里云登录mysql出现的1045错误
  16. 【Unity】Unity实现鼠标控制摄像机围绕物体旋转镜头 滑轮控制远近
  17. Java如何学才能快速入门(零基础入门)
  18. 数字内容市场水量上涨,阜博集团能否成为“鲶鱼”?
  19. 秒杀所有区间相关问题
  20. 仿 Lenovo商城首页

热门文章

  1. amd显卡测试大风车软件md,知之实验室 篇三:大家好才是真的好!免费显卡升级工具AMD FSR技术研究测试...
  2. iOS开发-舒尔特表
  3. TP6使用守护进程完成队列任务
  4. 性能监控 TP 指标
  5. R语言和Julia以及Python使用Feather包共享数据
  6. 八哥英语词根词缀汇总
  7. 影响一生的32步电影
  8. vitamio视频框架使用详解
  9. 2022年BAT,京东,美团,滴滴等公司面试经验总结
  10. 基于图像的火焰识别调研总结