一篇文章带你了解JVM垃圾回收

目录
  • 1.堆空间的基本结构:
  • 2.空间分配担保机制
  • 3.如何判断一个对象已经无效
  • 4 不可达的对象并非“非死不可”
  • 5 如何判断一个常量是废弃常量?
  • 6 如何判断一个类是无用的类
  • 7.垃圾回收算法
    • 7.1 标记-清除算法
    • 7.2 标记-复制算法
    • 7.3 标记-整理算法
    • 7.4 分代收集算法
  • 总结

如何判断对象是否死亡(两种方法)。

简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。

如何判断一个常量是废弃常量

如何判断一个类是无用的类

垃圾收集有哪些算法,各自的特点?

HotSpot 为什么要分为新生代和老年代?

常见的垃圾回收器有哪些?

介绍一下 CMS,G1 收集器。

Minor Gc 和 Full GC 有什么不同呢?

1.堆空间的基本结构:

现在的垃圾回收器基本上都采用分代垃圾回收算法,可分为新生代和老年代,新生代又可分为Eden区、From Survivor0、To Survivor区。

对象首先在eden区域分配,再经历一次垃圾回收后,如果对象还存会,就年龄加一,并进入From Survive区,当年龄增加到一定程度(默认15岁)时,会进入老年代。对象进入老年代的年龄阈值可通过-Xx:MaxTenuringThreshold设置,这个值会在虚拟机运行过程中进行调整,HotSpot遍历所有对象,按照年龄从小到大累计占用的区域,当累计区域超过一半时,取此时的年龄和设置的-Xx:MaxTenuringThreshold中较小值作为晋升老年代的年龄阈值。

针对 HotSpot VM 的实现,它里面的 GC 其实准确分类只有两大种:

部分收集 (Partial GC):

  • 新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;
  • 老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集;
  • 混合收集(MixedGC):对整个新生代和部分老年代进行垃圾收集。

整堆收集 (Full GC):

  • 收集整个 Java 堆和方法区。

2.空间分配担保机制

为了确保Major GC时,老年代有足够的空间容纳新生代的所有对象。

《深入理解Java虚拟机》第三章对于空间分配担保的描述如下:

JDK 6 Update 24 之前,在发生 Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次 Minor GC可以确保是安全的。如果不成立,则虚拟机会先查看 -XX:HandlePromotionFailure参数的设置值是否允许担保失败(Handle Promotion Failure);如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次 MinorGC,尽管这次 Minor GC 是有风险的;如果小于,或者 -XX: HandlePromotionFailure设置不允许冒险,那这时就要改为进行一次 Full GC。
JDK 6 Update 24之后的规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行 MinorGC,否则将进行 Full GC。

3.如何判断一个对象已经无效

引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。

这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。

2.2 可达性分析算法

这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为

引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。

可作为 GC Roots 的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈(Native 方法)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 所有被同步锁持有的对象

4 不可达的对象并非“非死不可”

即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。

被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。

5 如何判断一个常量是废弃常量?

运行时常量池主要回收的是废弃的常量。那么,我们如何判断一个常量是废弃常量呢?

JDK1.7 之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时 hotspot 虚拟机对方法区的实现为永久代
JDK1.7字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot 中的永久代 。
JDK1.8 hotspot 移除了永久代用元空间(Metaspace)取而代之,这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)

假如在字符串常量池中存在字符串 “abc”,如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 “abc” 就是废弃常量,如果这时发生内存回收的话而且有必要的话,“abc” 就会被系统清理出常量池了。

6 如何判断一个类是无用的类

方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?

判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 “无用的类” :

  • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
  • 加载该类的ClassLoader 已经被回收。 该类对应的
  • java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

7.垃圾回收算法

7.1 标记-清除算法

该算法分为“标记”和“清除”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。

它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题:

效率问题

空间问题(标记清除后会产生大量不连续的碎片)

7.2 标记-复制算法

为了解决效率问题,“标记-复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。

7.3 标记-整理算法

根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。

7.4 分代收集算法

当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。

比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。

总结

这篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 浅谈JVM垃圾回收有哪些常用算法

    一.前言: 垃圾回收: 在未来的JDK中可能G1会为ZGC所取代 先问自己几个问题: 什么是垃圾? 垃圾就是堆内存中(范指)没有任何指针指向的对象实体.不具有可达性. 为什么要回收垃圾? 因为我们的内存是有限的,内存长时间不清理就会导致内存溢出,OOM: 只要是程序正在跑,那么就不断生成新的对象,我们需要GC开辟新的空间分配给新的对象. 我们怎么回收垃圾? 依靠Java的自动内存回收机制,机制的优劣由算法决定: 或者说是机制的适配度由算法和应用场景共同决定. 什么时候回收垃圾? 当堆中的实体对象

  • 新手入门Jvm--Jvm垃圾回收

    目录 1. Jvm垃圾回收 2. 作用域 3. 分类 4. 垃圾回收算法 4.1 标记-复制算法 4.2 标记-清除算法 4.3 标记-整理算法 5. 垃圾收集器 5.1 Serial收集器(-XX:+UseSerialGC -XX:+UseSerialOldGC) 5.2 Parallel Scavenge收集器(-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代)) 5.3 ParNew收集器(-XX:+UseParNewGC) 5.4 CMS

  • JVM类加载,垃圾回收

    目录 类加载子系统 双亲委派模型 垃圾回收 判断对象已死 JDK1.2之后的四种引用类型: 1.强引用: 2.软引用: 3.弱引用: 4.虚引用: 常见的垃圾回收算法: 1.标记–清除算法:(Mark–Sweep) 2.标记–复制算法: 3.标记–整理算法: 常见的垃圾回收器: 新时代.老年代 为什么大对象会直接存在老年代? 总结 类加载子系统 classLoader 只负责对字节码文件的加载,至于是否可以运行,还要看执行引擎. 加载的类信息存放于方法区的内存空间,除了类信息之外,还会存放有运行

  • 深入理解JVM垃圾回收算法

    目录 一.垃圾标记阶段 1.1.引用计数法 (java没有采用) 1.2.可达性分析算法 二.对象的finalization机制 2.1.对象是否"死亡" 三.使用(MAT与JProfiler)工具分析GCRoots 3.1.获取dump文件 3.2.GC Roots分析 四.垃圾清除阶段 4.1.标记-清除算法 4.2.复制算法 4.3.标记-压缩(整理,Mark-Compact)算法 4.4.以上三种垃圾回收算法对比 4.5.分代收集算法 4.6.增量收集算法 4.7.分区算法G1

  • JVM执行引擎和垃圾回收要点总结

    目录 一.执行引擎 二.垃圾对象标记 1.引用计数法 2.可达性分析算法 三.垃圾回收机制 1.标记清除算法 2.标记整理算法 3.复制算法 4.分代收集算法 四.源代码地址 一.执行引擎 应用程序经过编译,转换为字节码文件,字节码加载到内存空间并不能直接在操作系统上执行,执行引擎作为Java虚拟机核心的组成部分,作用就是将字节码指令解释/编译为对应系统平台上的本地机器指令. 解释器:虚拟机启动时会根据预定义对字节码采用逐行解释的方式执行,将每条字节码文件中的内容解释为对应系统平台的本地机器指令

  • 一篇文章带你了解JVM垃圾回收

    目录 1.堆空间的基本结构: 2.空间分配担保机制 3.如何判断一个对象已经无效 4 不可达的对象并非"非死不可" 5 如何判断一个常量是废弃常量? 6 如何判断一个类是无用的类 7.垃圾回收算法 7.1 标记-清除算法 7.2 标记-复制算法 7.3 标记-整理算法 7.4 分代收集算法 总结 如何判断对象是否死亡(两种方法). 简单的介绍一下强引用.软引用.弱引用.虚引用(虚引用与软引用和弱引用的区别.使用软引用能带来的好处). 如何判断一个常量是废弃常量 如何判断一个类是无用的类

  • 一篇文章带你了解JVM内存模型

    目录 1. JVM介绍 1.1 什么是JVM? 1.2 JVM的优点 1.2.1 一次编写,到处运行. 1.2.2 自动内存管理,垃圾回收机制. 1.2.3 数组下标越界检查 1.2.4 多态 1.3 JVM.JRE.JDK之间的关系 1.3.1 JVM的简介 1.3.2 JRE的简介 1.3.3 JDK的简介 1.4 JVM的常见实现 1.5 JVM的内存结构图 1.5.1方法区.堆 1.5.2虚拟机栈.程序计数器.本地方法栈 1.5.3执行引擎 1.5.4 GC(垃圾回收机制) 1.5.5本

  • 一篇文章带你了解Java中ThreadPool线程池

    目录 ThreadPool 线程池的优势 线程池的特点 1 线程池的方法 (1) newFixedThreadPool (2) newSingleThreadExecutor (3) newScheduledThreadPool (4) newCachedThreadPool 2 线程池底层原理 3 线程池策略及分析 拒绝策略 如何设置maximumPoolSize大小 ThreadPool 线程池的优势 线程池做的工作主要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些

  • jvm垃圾回收之GC调优工具分析详解

    进行GC性能调优时, 需要明确了解, 当前的GC行为对系统和用户有多大的影响.有多种监控GC的工具和方法, 本章将逐一介绍常用的工具. JVM 在程序执行的过程中, 提供了GC行为的原生数据.那么, 我们就可以利用这些原生数据来生成各种报告.原生数据(raw data) 包括: 各个内存池的当前使用情况, 各个内存池的总容量, 每次GC暂停的持续时间, GC暂停在各个阶段的持续时间. 可以通过这些数据算出各种指标, 例如: 程序的内存分配率, 提升率等等.本章主要介绍如何获取原生数据. 后续的章

  • 一篇文章带你搞懂Java线程池实现原理

    目录 1. 为什么要使用线程池 2. 线程池的使用 3. 线程池核心参数 4. 线程池工作原理 5. 线程池源码剖析 5.1 线程池的属性 5.2 线程池状态 5.3 execute源码 5.4 worker源码 5.5 runWorker源码 1. 为什么要使用线程池 使用线程池通常由以下两个原因: 频繁创建销毁线程需要消耗系统资源,使用线程池可以复用线程. 使用线程池可以更容易管理线程,线程池可以动态管理线程个数.具有阻塞队列.定时周期执行任务.环境隔离等. 2. 线程池的使用 /** *

  • 一篇文章带你弄清楚Redis的精髓

    目录 一.Redis的特性 1.1 Redis为什么快? 1.2 Redis其他特性 1.3 Redis高可用 二.Redis数据类型以及使用场景 2.1 String 2.1.1 基本指令 2.1.2 应用场景 2.2 Hash 2.2.1 基本指令 2.2.2 应用场景 2.3 List 2.3.1 基本指令 2.3.2 应用场景 2.4 Set 2.4.1 基本指令 2.4.2 应用场景 2.5 ZSet(SortedSet) 2.5.1 基本指令 2.5.2 应用场景 三.Redis的事

  • 一篇文章带你使用Typescript封装一个Vue组件(简单易懂)

    一.搭建项目以及初始化配置 vue create ts_vue_btn 这里使用了vue CLI3自定义选择的服务,我选择了ts.stylus等工具.然后创建完项目之后,进入项目.使用快捷命令code .进入Vs code编辑器(如果没有code .,需要将编辑器的bin文件目录地址放到环境变量的path中).然后,我进入编辑器之后,进入设置工作区,随便设置一个参数,这里比如推荐设置字号,点下.这里是为了生成.vscode文件夹,里面有个json文件. 我们在开发项目的时候,项目文件夹内的文件很

  • 浅析JVM垃圾回收的过程

    JVM垃圾回收的算法很多,但是不管是哪种算法,在进行GC时大致的流程都是差不多的,主要有以下3个过程: 1. 枚举根节点 这个过程主要是找到所有的GC Roots对象,这些对象一般发生在JVM虚拟机栈栈帧.常量池中的静态对象.方法区中静态类属性引用.本地方法栈中引用的对象.这个过程会发生STW,所有的线程均运行到安全区域(Safe Region)才开始执行. 通常有两种算法: 引用计数法:每个对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就+1:当引用失效时,计数器值就-1:任何时刻

  • 一篇文章带你搞定SpringBoot中的热部署devtools方法

    一.前期配置 创建项目时,需要加入 DevTools 依赖 二.测试使用 (1)建立 HelloController @RestController public class HelloController { @GetMapping("/hello") public String hello(){ return "hello devtools"; } } 对其进行修改:然后不用重新运行,重新构建即可:只加载变化的类 三.热部署的原理 Spring Boot 中热部

  • 一篇文章带你搞定SpringBoot不重启项目实现修改静态资源

    一.通过配置文件控制静态资源的热部署 在配置文件 application.properties 中添加: #表示从这个默认不触发重启的目录中除去static目录 spring.devtools.restart.exclude=classpath:/static/** 或者使用: #表示将static目录加入到修改资源会重启的目录中来 spring.devtools.restart.additional-paths=src/main/resource/static 此时对static 目录下的静态

随机推荐