JVM虚拟机系统性学习-对象存活判断算法、对象引用类型和垃圾清除算法

本文主要是介绍JVM虚拟机系统性学习-对象存活判断算法、对象引用类型和垃圾清除算法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

垃圾回收

在 JVM 中需要对没有被引用的对象,也就是垃圾对象进行垃圾回收

对象存活判断算法

判断对象存活有两种方式:引用计数法、可达性分析算法

引用计数法

引用计数法通过记录每个对象被引用的次数,例如对象 A 被引用 1 次,就将 A 的引用计数器加 1,当其他对象对 A 的引用失效了,就将 A 的引用计数器减 1

  • 优点:
    • 实现简单,判定效率高
  • 缺点:
    • 需要单独的字段存储计数器,增加存储空间开销
    • 每次赋值都要更新计数器,增加时间开销
    • 无法处理循环引用的情况,致命问题!即 A 引用 B,B 引用 A,那么他们两个的引用计数器永远都为 1

可达性分析算法

可达性分析算法可以有效解决循环引用的问题,Java 选择了这种算法

可达性分析算法以根对象集合(GC Roots)为起使点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达,通过可达性分析算法分析后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索过程所走过的路径称为引用链,如果目标对象没有任何引用链相连,则是不可达的,就可以标记为垃圾对象

GC Roots 主要包含以下几类元素:

  • 虚拟机栈中引用的对象

    如:各个线程被调用的方法中所使用的参数、局部变量等

  • 本地方法栈内的本地方法引用的对象

  • 方法区中引用类型的静态变量

  • 方法区中常量引用的对象

    如:字符串常量池里的引用

  • 所有被 synchronized 持有的对象

  • Java 虚拟机内部的引用

    如:基本数据类型对应的 Class 对象、异常对象(如 NullPointerException、OutOfMemoryError)、系统类加载器

垃圾回收过程

在 Java 中对垃圾对象进行回收需要至少经历两次标记过程:

  • 第一次标记:如果经过可达性分析后,发现没有任何引用链相连,则会第一次被标记
  • 第二次标记:判断第一次标记的对象是否有必要执行 finalize() 方法,如果在 finalize() 方法中没有重新与引用链建立关联,则会被第二次标记

第二次被标记成功的对象会进行回收;否则,将继续存活

对象的 finalization 机制:

Java 提供了 finalization 机制来允许开发人员 自定义对象被销毁之前的处理逻辑,即在垃圾回收一个对象之前,会先调用这个对象的 finalize() 方法,该方法允许在子类中被重写,用于在对象被回收时进行资源释放的工作

对象引用

在 JDK1.2 之后,Java 对引用的概念进行了扩张,将引用分为强引用(StrongReference)、软引用(SoftReference)、弱引用(WeakReference)、虚引用(PhantomReference)四种,这四种引用强度依次逐渐减弱

  • 强引用-不回收:强引用是最普遍的对象引用,也是默认的引用类型,强引用的对象是可触及的,垃圾回收器永远不会回收被引用的对象,因此强引用是造成Java内存泄漏的主要原因之一

    • 当使用new操作创建一个新对象时,并且将其赋值给一个变量时,这个变量就成为该对象的一个强引用
  • 软引用-内存不足回收:在即将发生内存溢出时,会将这些对象列入回收范围进行第二次回收,如果回收之后仍然没有足够的内存,则会抛出内存溢出异常

    • 软引用通常用来实现内存敏感的缓存,例如高速缓存使用了软引用,如果内存足够就暂时保留缓存;如果内存不足,就清理缓存

      // 创建弱引用
      SoftReference<User> softReference = new SoftReference<>(user);
      // 从软引用中获取强引用对象
      System.out.println(softReference.get());
      
  • 弱引用-发现即回收:被弱引用关联的对象只能存活在下一次垃圾回收之前,在垃圾回收时,无论空间是否足够,都会会受掉被弱引用关联的对象

    • 弱引用常用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的 isEnQueued 方法判断对象是否被垃圾回收器标记

      Object obj = new Object();
      WeakReference<Object> wf = new WeakReference<Object>(obj);
      obj = null;
      // System.gc();
      // 有时候会返回null
      Object o = wf.get(); 
      // 返回是否被垃圾回收器标记为即将回收的垃圾
      boolean enqueued = wf.isEnqueued(); 
      System.out.println("o = " + o);
      System.out.println("enqueued = " + enqueued);
      
  • 虚引用:垃圾回收时,直接回收,无法通过虚引用获取对象实例

    • 为一个对象设置虚引用关联的唯一目的就是能在这个对象被垃圾回收时收到一个系统通知

      Object obj = new Object();
      PhantomReference<Object> pf = new PhantomReference<Object>(obj, new
      ReferenceQueue<>());
      obj=null;
      // 永远返回null
      Object o = pf.get();
      // 返回是否从内存中已经删除
      boolean enqueued = pf.isEnqueued();
      System.out.println("o = " + o);
      System.out.println("enqueued = " + enqueued);
      

垃圾清除算法

GC最基础的算法有三种: 标记 -清除算法、复制算法、标记-压缩算法,我们常用的垃圾回收器一般都采用分代收集算法。

  • 标记-清除算法:在标记阶段,从 GC Roots 开始遍历,标记所有被引用的对象,标记为可达对象,再对堆内存从头到尾遍历,回收没有标记为可达对象的对象(标记清除算法可以标记存活对象也可以标记待回收对象)

    • 这里并不是真正清除,而是将清除对象的地址放在空闲的地址列表中
    • 缺点
      • 效率不高
      • GC 时需要停止整个应用进程,用户体验不好
      • 会产生内存碎片

    在这里插入图片描述

  • 复制算法:它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉

    现在商用的 Java 虚拟机大多都优先采用这种收集算法去回收新生代,如果将内存区域划分为容量相同的两部分太占用空间,因此将复制算法进行了优化,优化后将新生代分为了 Eden 区、Survivor From 区、Survivor To 区,Eden 和 Survivor 的大小比例为 8:1:1,每次分配内存时只使用 Eden 和其中的一块 Survivor 区,在进行垃圾回收时,将 Eden 和已经使用过的 Survivor 区的存活对象转移到另一块 Survivor 区中,再清理 Eden 和已经使用过的 Survivor 区域,当 Survivor 区域的空间不足以容纳一次 Minor GC 之后存活的对象时,就需要依赖老年代进行分配担保(通过分配担保机制,将存活的对象放入老年代即可)

    • 优点
      • 实现简单,运行高效
      • 复制之后,保证空间的连续性,不会出现“内存碎片”
    • 缺点
      • 存在空间浪费
    • 应用场景
      • 在新生代,常规的垃圾回收,一次可以回收大部分内存空间,剩余存活对象不多,因此现在的商业虚拟机都是用这种收集算法回收新生代

    在这里插入图片描述

  • 标记-压缩算法:标记过程仍然与“标记-清除”算法一样,之后将所有的存活对象压到内存的一端,按顺序排放,之后,清理边界外的内存

    • 优点
      • 解决了标记-清除算法出现内存碎片的问题
      • 解决了复制算法中空间浪费的问题
    • 缺点
      • 效率上低于复制算法
      • 移动对象时,如果对象被其他对象引用,则还需要调整引用的地址
      • 移动过程中,需要暂停用户应用程序。即 STW

    在这里插入图片描述

  • 分代收集算法:把 Java 堆分为新生代和老年代,这样就可以对不同生命周期的对象采取不同的收集方式,以提高回收效率

    当前商业虚拟机都采用这种算法

    • 新生代中的对象生命周期短,存活率低,因此适合使用复制算法(存活对象越少,复制算法效率越高)
    • 老年代中对象生命周期长,存活率高,回收没有新生代频繁,一般使用标记-清除或者是标记-压缩

这篇关于JVM虚拟机系统性学习-对象存活判断算法、对象引用类型和垃圾清除算法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/488526

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系