1.简述

在Java内存运行时区域的各个部分中,程序计数器、虚拟机栈、本地方法栈3个区域随着线程而生,随着线程而亡。栈中的栈帧随着方法的进入和退出而有条不紊的进行着入栈和出栈操作。
每个栈帧需要分配多少内存基本上在类结构确定时就已知了,因此这几个区域的内存分配和回收具有可确定性,在这几个区域就不需要过多的考虑内存的分配和回收问题,因为方法结束或线程结束时,内存自然就释放了。而java堆和方法区不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,只有在程序运行期间才能知道会创建哪些对象,这部分的内存分配和回收都是动态的,垃圾收集器所关注就是这部分内存,因此本文的后续讨论中的“内存”分配与回收也仅指这部分内存

2.如何判断对象死亡

在堆里存放着java几乎所有的对象实例,垃圾收集器在对堆中的内存进行回收时,第一件事情就是要确定哪些对象是“存活”的,哪些对象是“死亡”的。那么如何判断对象是否可回收呢,引用计数器算法和可达性分析法

2.1.引用计数算法

引用技术算法就是,给对象添加一个引用计数器,当对象被引用时计数器就加1;当引用失效时,计数器就减1;当计数器为0时,表示对象就不能再被使用了。

使用引用计数算法判断对象的存活,方法很简单,但是该方法很难解决对象之间循环引用的问题。

举个简单的例子:
对象objA和对象objB都有字段instance,对其赋值objA.instance=objB、objB.instance=objA,此时对象objA和objB互相引用着对方,导致他们的计数器都不为0,于是引用计数算法无法通知垃圾收集器回收他们

2.1.2.可达性分析法

通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象时不可用的。如图:对象object5,object6,object7虽然相互有关联,但是他们到GC Roots是不可达的,所以它们将会被判定为可回收对象

在Java语言中,可作为GC Roots的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中的引用对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(Native方法)引用的对象

2.1.3.Java引用

无论是通过引用计数器算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判断对象存活都与“引用”有关。

Java将引用分为强引用(Strong Reference)、软引用(Weak Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这四种引用强度一次逐渐减弱。

强引用(Strong Reference)

强引用是使用最普遍的引用。如果一个对象具有强引用,垃圾收集器永远不会回收掉被引用的对象

Object obj=new Object() //强引用

当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会随意回收具有强引用的对象来解决内存不足的问题。

如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。

obj=null //告诉垃圾收集器此对象可以被回收

软引用(Weak Reference)

软引用是用来描述一些有用但非必须的对象。

String str=new String("123");                                     // 强引用
SoftReference<String> softRef=new SoftReference<String>(str);     // 软引用

如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;

如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

软引用可用来实现内存敏感的高速缓存,比如网页缓存、图片缓存等。使用软引用能防止内存泄露,增强程序的健壮性。
SoftReference的特点是它的一个实例保存对一个Java对象的软引用, 该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。

也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对 这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。

另外,一旦垃圾线程回收该Java对象之 后,get()方法将返回null。

弱引用(Weak Reference)

弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示

public class WeakReferenceDemo {public static void main(String[] args) {WeakReference<User> reference=new WeakReference<User>(new User("make",19));System.out.println(reference.get());System.gc();//通知垃圾收集器回收资源System.out.println(reference.get());}@Data@AllArgsConstructorpublic static class User{private String name;private int age;}
}

输出

WeakReferenceDemo.User(name=make, age=19)
null

第二个输出结果是null,这说明只要JVM进行垃圾回收,被弱引用关联的对象必定会被回收掉。

不过要注意的是,这里所说的被弱引用关联的对象是指只有弱引用与之关联,如果存在强引用同时与之关联,则进行垃圾回收时也不会回收该对象(软引用也是如此),稍微修改一下代码,再看一下输出结果:

public class WeakReferenceDemo {public static void main(String[] args) {User user=new User("make",19);//强引用WeakReference<User> reference=new WeakReference<User>(user);System.out.println(reference.get());System.gc();//通知垃圾收集器回收资源System.out.println(reference.get());}@Data@AllArgsConstructorpublic static class User{private String name;private int age;}
}

输出

WeakReferenceDemo.User(name=make, age=19)
WeakReferenceDemo.User(name=make, age=19)

虚引用(Phantom Reference)

虚引用也称为幽灵引用或者幻影引用,他是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对它的生命周期造成影响,也无法通过虚引用来获取一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知,在Java中通过PhantomReference类来实现虚引用。

虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

引用总结

引用级别由高到低:强引用 > 软引用 > 弱引用 > 虚引用

引用类型 回收时机 用途 生存时间
强引用 从不 一般对象 jvm运行终止
软引用 内存不足时 对象缓存 内存不足时终止
弱引用 垃圾回收时 对象缓存 垃圾回收后终止
虚引用 未知 垃圾回收通知 垃圾回收后终止

3.对象的自我救赎

即使在可达性分析算法中不可达的对象,也并非是“非死不可”,这时候它处于待回收阶段,要真正的宣告一个对象的死亡,至少要经历两次标记过程:

第一次,可达性分析后发现对象不可达,将会被第一次标记并进行一次筛选,筛选条件是判断该对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或已经被虚拟机调用过finalize()方法,虚拟机将这两种情况称为“没有必要执行”

第二次,如果这个对象被判定为有必要执行finalize()方法,那么这个对象会被放在一个叫做F-Queue的队列里,并在稍后由一个虚拟机自动创建的、低优先级的Finalizer线程去执行它。finalize()方法是对象逃脱死亡的最后一次机会,稍后GC将对F-Queue中的对象进行第二次标记。如果对象在finalize()中成功拯救自己,重新与引用链上的任何一个对象建立了关联,那么第二次标记将把它移除“即将回收”的集合。否则,就会真正被回收

对象自我救赎实例:

package com.lkf.jvm;/*** 此代码演示了两点:* 一是:对象可以被GC自我救赎* 二是:这种自我救赎的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次**/
public class FinalizeEscapeGCDemo {public static FinalizeEscapeGCDemo SAVE_HOOK = null;public void isAlive() {System.out.println("Yes,I'm still alive :)");}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("finalize method executed!");FinalizeEscapeGCDemo.SAVE_HOOK = this;}public static void main(String[] args) throws Throwable {SAVE_HOOK = new FinalizeEscapeGCDemo();//对象第一次成功拯救自己SAVE_HOOK = null;System.gc();//因为finalize方法优先级很低,所以暂停0.5秒等待它Thread.sleep(500);if (SAVE_HOOK != null) {SAVE_HOOK.isAlive();} else {System.out.println("No,I'm dead :)");}//下面这段代码与上面的完全相同,但是这次自救却失败了SAVE_HOOK = null;System.gc();//因为finalize方法优先级很低,所以暂停0.5秒等待它Thread.sleep(500);if (SAVE_HOOK != null) {SAVE_HOOK.isAlive();} else {System.out.println("No,I'm dead :)");}}
}

输出:

finalize method executed!
Yes,I'm still alive :)
No,I'm dead :)

JVM:GC之对象生死相关推荐

  1. JVM GC之对象生死

    1.简述 在Java内存运行时区域的各个部分中,程序计数器.虚拟机栈.本地方法栈3个区域随着线程而生,随着线程而亡.栈中的栈帧随着方法的进入和退出而有条不紊的进行着入栈和出栈操作. 每个栈帧需要分配多 ...

  2. 备战面试日记(2.4) - (JVM.GC算法)

    本人本科毕业,21届毕业生,一年工作经验,简历专业技能如下,现根据简历,并根据所学知识复习准备面试. 记录日期:2022.1.2 大部分知识点只做大致介绍,具体内容根据推荐博文链接进行详细复习. 文章 ...

  3. 深入理解JVM(三)——JVM之判断对象是否存活(引用计数算法、可达性分析算法,最终判定),Eclipse设置GC日志输出,引用

    本文转载自https://blog.csdn.net/ochangwen/article/details/51406779 本文是基于周志明的<深入理解Java虚拟机> 堆中几乎存放着Ja ...

  4. java服务器gc停顿_如何避免后台IO高负载造成的长时间JVM GC停顿(转)

    译者著:其实本文的中心意思非常简单,没有耐心的读者建议直接拉到最后看结论部分,有兴趣的读者可以详细阅读一下. 原文发表于Linkedin Engineering,作者 Zhenyun Zhuang是L ...

  5. JVM GC 日志详解

    本文采用的JDK版本: java version "1.8.0_144" Java(TM) SE Runtime Environment (build 1.8.0_144-b01) ...

  6. Kafka如何在千万级别时优化JVM GC问题?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | https://www.toutiao.com ...

  7. JVM - 剖析Java对象头Object Header之指针压缩

    文章目录 Pre 指针压缩 论证压缩效果 UseCompressedOops & UseCompressedClassPointers [指针压缩]开启 VS 关闭 指针压缩的目的 为什么堆内 ...

  8. JVM - 剖析Java对象头Object Header之对象大小

    文章目录 Pre 总览 对象头剖析 查看对象内存的占用情况 对象头C++源码 注释 Pre JVM - 写了这么多年代码,你知不道new对象背后的逻辑? 中大体介绍了Java中 new 对象背后的主要 ...

  9. 面试官问:上亿数据量下,Kafka是如何优化JVM GC问题的?

    大家都知道Kafka是一个高吞吐的消息队列,是大数据场景首选的消息队列,这种场景就意味着发送单位时间消息的量会特别的大,那既然如此巨大的数据量,kafka是如何支撑起如此庞大的数据量的分发的呢? 今天 ...

最新文章

  1. Hadoop安装的ssh免密码登录步骤
  2. 机器学习速成课程 | 练习 | Google Development——编程练习:合成特征和离群值
  3. mybatis 取查询值_MyBatis面试题集合,90%会遇到这些问题
  4. cogs62 [HNOI2004] 宠物收养所
  5. 网管必杀技之VLAN的网络管理
  6. SqlHelper方法
  7. 微信小程序input标签详解
  8. 计算机专硕学硕哪个好考啊,【专硕考研】计算机考研选学硕还是专硕?
  9. java+ElementUI前后端分离旅游项目第二天 旅游管理和自由行
  10. 中印程序员对比:是什么让我们觉得印度程序员很厉害?
  11. 万字长文读透 Redis
  12. Vue 移动端调用相机和相册实现图片上传
  13. excel怎么不显示图表上显示为0%的项?
  14. RedHat Linux两大产品 AS与ES
  15. Boll布林带波动率策略
  16. 小程序如何关联企业微信
  17. 一对一直播源码 一对一视频交友源码开发
  18. mysql 手工sql注入_【sql注入专题02】Mysql手工注入流程
  19. vb+bat的种种常用代码大全(2020/2/27 #4版)
  20. 帧同步分离逻辑层和渲染层_帧同步的一些坑

热门文章

  1. 【Java|golang】2611. 老鼠和奶酪
  2. 操作系统实验4——内核线程管理
  3. 更相减损术,欧拉函数
  4. 一个基于 Transformer 的深度学习架构,在基因调控中组蛋白代码的定量破译方面性能超群...
  5. ZUCC_Object Oriented Programming_Lab09_assignmentCourseZucc_chapter12
  6. 激活Office时提示0xc004f074错误
  7. c语言算24点答案,C语言-纸牌计算24点小游戏
  8. 儿童节,送点礼物给你们
  9. 技术女性职业规划及其它
  10. 手把手教你正确安装Python,学Python必看