为什么我们在写Java程序的时候没有过多地去关注内存的分配和回收问题?因为JVM会帮助我们完成这两个工作,准确的说是JVM内存管理器来完成的。内存管理器(Memroy Collector)通常我们又叫它垃圾回收器(Garbage Collector)也就是GC。

在上一篇博客中我们讲了JVM运行时内存分区,大体上分为线程共享和线程私有,线程私有的分区有Java虚拟栈,本地方法栈和程序计数器,对于这三个内存分区的具体分配和回收是显而易见的,他们的生命周期与线程的创建和结束同步,是确定的,因此这篇博客我们讨论方法区和堆区(重点)。

回收堆区

堆中存放着几乎所有的对象实例,垃圾回收器在对堆进行垃圾回收之前,首先要判断这些对象哪些还“存活”,哪些已经“死去”。
这里有两类算法,第一类算法是专门用来标记出哪些对象已经不再被使用,第二类算法是专门回收被标记的对象。

标记算法

引用计数法

引用计数描述的算法为:在对象内部设置一个额外的空间专门用来存储当前指向该对象的引用数目,给它起名字叫引用计数器,每次有一个新的引用指向该对象时,计数器+1;相应的每当一个引用失效,计数器就-1。当计数器 == 0的时候就代表没有引用指向该对象,即该对象被标记“已死”。

该算法有一个弊端——无法解决对象的循环引用问题。
什么是对象的循环引用呢?
举个例子

public class Person{Person per;public static void main(String[] args){Person p = new Person();p.per = p; }
}

画图理解:

这样就会导致要去标记该对象死亡,但是始终有引用指向它,出现了这样一个死循环的问题,这就是对象的循环引用问题。正因为这个弊端,JVM的垃圾回收器就没用用这个算法,JVM采用的是下面这个可达性分析算法。

可达性分析算法

这是标记对象“已死”的第二种算法,此算法的核心思想为:有一个叫做GC Roots 的Set集合,该集合内部放着很多被称为GC Roots的对象,可达性分析算法是每次从这些GC Roots开始向下搜索,凡是有对象不在以任何一个以GC Roots为起点的引用链,即从GC Roots到这个对象不可达时,就等于标记这些不可达的对象“已死”。
以下图为例:

对象Object5 – Object7之间虽然彼此还有关联,但是它们到GC Roots是不可达的,因此他们会被判定为可回收对象。
在Java语言中,可作为GC Roots的对象包含下面几种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  2. 方法区中类静态属性引用的对象;
  3. 方法区中常量引用的对象;
  4. 本地方法栈中引用的对象;

以上两个算法只是判断对象“生死”,我们只是在进行标记。
下面再讨论回收算法之前我们先来介绍一下几种引用

引用分类

在JDK1.2以前,Java中引用的定义很传统:如果引用类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。这种定义有些狭隘,一个对象在这种定义下只有被引用或者没有被引用两种状态。
我们希望能描述这一类对象:当内存空间足够时,则能保在内存中;如果内存空间在进行垃圾回收后还是非常紧张,则可以抛弃这些对象。很多系统中的缓存对象都符合这样的场景。
在JDK1.2之后,Java对引用的概念做了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)四种,这四种引用的强度依次递减。

  1. 强引用:强引用指的是在程序代码之中普遍存在的,只要还有强引用指向一个对象,内存管理器就一直不会回收掉被该引用的对象。
  2. 软引用:软引用是来描述一些还有用但是不是必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出之前,会把这些对象列入回收范围之中进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常。在JDK1.2之后停工了SoftReference类来实现软引用。
  3. 弱引用:弱引用的强度要弱于软引用,它是用来描述非必须对象的。被弱引用关联的对象只能生存到下一次垃圾回收发生之前。一旦GC开始工作时,它不管当前内存是否够用,都会回收掉所有只被弱引用关联的对象。在JDK1.2之后提供了WeakReference来实现弱引用。
  4. 虚引用:它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK1.2之后,提供了PhantomReference类来实现虚引用。

垃圾回收算法

标记清除算法

标记-清除算法是最基础的回收算法。算法有两个步骤,第一步是标记所有要被回收的对象,在第一步标记结束后统一回收所有已经被标记过的对象这个步骤称为清除。但是这个算法有两个不足的地方:

  1. 效率问题: 标记和清除这两个过程效率都不高。
  2. 空间问题: 标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行中需要分配大空间时,无法找到足够连续内存。
    上面就是标记清除算法大概流程和它的不足,此后的收集算法都是在这个算法的基础思想之上改进的。

分代收集算法

当前JVM垃圾收集都采用的是“分代收集”算法,这个算法并没有新思想,只是根据对象存活周期不同将内存划分为几块。
一般是把JVM中堆分为新生代和老年代。新生代中,每次垃圾回收都会有大批对象死去,只有少量存活,因此我们采用复制算法(下面有介绍);而老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用“标记-整理”算法(下面有介绍)

复制算法(新生代回收算法)

复制算法是为了解决“标记-清理”的效率问题。该算法的主要思想为将可用内存按容量划分为两块大小相等的区域,每次只使用两块中的一块。当第一块内存需要进行垃圾回收时,会把这个区域还活着的对象复制到另一块上面,然后再第一块内存区域全部回收。这样做的好处是每次都是对整个半区进行内存回收,也就空出了整个半区的连续内存空间,内存分配时也就不需要考虑内存碎片等情况,只需要移动堆顶指针,按顺序分配即可。此算法实现简单,运行高效。算法的执行流程如下图:

现在的商用虚拟机(包括HotSpot都是采用这种收集算法来回收新生代)
新生代中98%的对象存活时间都很短,基本都算是“朝生夕死”的,所以没有必要按照1:1的比例来划分内存空间,而是将内存(新生代内存)分为一块Eden空间和两块Survivor空间,每次使用Eden和Survivor其中一块(两个Survivor一块称为From区,一块称为To区)。当回收时,将Eden和Survivor中还存活的对象一次性复制到另一块Survivor空间,最后清理掉Eden和刚才用过的Survivor空间。

当Survivor空间不够用时,需要依赖其他内存(老年代)进行分配。

HotSpot默认Eden与Survivor的大小比例是8:1,也就是说Eden:Survivor From:Survivor To = 8:1:1。所以每次新生代可用内存空间为整个新生代容量的90%,而剩下的10%用来存放回收后存活的对象。
HotSpot实现的复制算法流程如下:

  1. 当Eden区满的时候,会触发第一次Minor gc把还活着的对象拷贝到Survivor From区;当Eden区再发生Minor gc时,就把Eden区和From区活着的对象复制到To区,并将Eden区和From区清空。
  2. 当后续Eden又发生Minor gc的时候,会对Eden和To区进行垃圾回收,存活的对象复制到From区域,并将Eden和To区域清空。
  3. 部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认15),最终如果还是存活就存入老年代。

标记-整理算法(老年代回收算法)

复制算法在对对象存活率较高时会进行较多的复制操作,效率会变低。因此在老年代一般不能使用复制算法。
针对老年代的特点,提出了一种称为“标记-整理”算法。标记过程仍采用可达性分析算法,但后续步骤不是直接对可回收对象进行清理,而是先让所有存活对象都向一端移动,移动之后把边界以外的全部内存全部回收。流程如下图所示:

回收方法区

方法区(永久代)的垃圾回收主要收集两部分内容:废弃常量和无用的类。
下面来具体说说什么是废弃常量和无用的类:

废弃常量

回收废弃常量和回收Java堆中的对象十分类似。以常量池中的字面量的回收为例,将入一个字符串“abc”已经进入了常量池中,但是当前没有任何一个String对象引用常量池的“abc”常量,如果此时发生GC并且有必要的话,这个“abc”常量会被系统清理出常量池。常量池中其他类(接口)、方法、字段的符号引用也与此类似。

无用的类

类需要同时满足下面向三个条件才会被算是“无用的类”:

  1. 该类所有实例都已经被回收(即在Java堆中不存在任何该类的实例)
  2. 加载该类的ClassLoader已经被回收
  3. 该类对应的Class对象没有在任何其他地方被引用,无法在任何地方通过反射访问该类的方法。 JVM可以对挺尸满足上述三个条件的“无用类”进行回收,也仅仅是可以而不是必然。

这里有一个面试题:请问Minor GC和Full gc有什么不一样的吗?

答:

  1. Minor gc又被称为新生代GC:指的是发生在新生代的垃圾收集。因为Java对象大都有朝生夕死的特性,因此Minor gc非常频繁,一般回收速度相对较快。Minor gc采用的算法是复制算法。
  2. Full gc又称为老年代gc或者 Major gc:指在发生在老年代的垃圾收集。出现了Major gc 经常伴随至少一次Minor gc(并非绝对,在Parallel Scavenge收集器中就有直接进行Full gc的策略选择过程)。Major gc的速度一般会比Minor gc慢十倍以上。

JVM垃圾回收,面试问到的都有了相关推荐

  1. 2020最全JVM垃圾回收机制面试题整理,阿里面试官最爱问的都在这里了(附答案)

    前言 为什么需要垃圾回收 首先我们来聊聊为什么会需要垃圾回收,假设我们不进行垃圾回收会造成什么后果,我们举一个简单的例子 我们住在一个房子里面,我们每天都在里面生活,然后垃圾都丢在房子里面,又不打扫, ...

  2. 【搞定Jvm面试】 JVM 垃圾回收揭秘附常见面试题解析

    JVM 垃圾回收 写在前面 本节常见面试题 问题答案在文中都有提到 如何判断对象是否死亡(两种方法). 简单的介绍一下强引用.软引用.弱引用.虚引用(虚引用与软引用和弱引用的区别.使用软引用能带来的好 ...

  3. JVM 垃圾回收算法 -可达性分析算法!!!高频面试!!!

    前言:学习JVM,那么不可避免的要去了解JVM相关的垃圾回收算法,本文只是讲了讲了可达性分析算法,至于标记-清除.标记-复制,标记-整理,分代收集等等算法,会在近两天的文章中陆续更新出来. 很喜欢一句 ...

  4. jvm gc垃圾回收机制和参数说明amp;amp;Java JVM 垃圾回收(GC 在什么时候,对什么东西,做了什么事情)

    jvm gc(垃圾回收机制) Java JVM  垃圾回收(GC 在什么时候,对什么东西,做了什么事情) 前言:(先大概了解一下整个过程) 作者:知乎用户 链接:https://www.zhihu.c ...

  5. jvm垃圾回收机制_JVM的垃圾回收机制总结

    一.了解技术背景 按照套路是要先装装X,谈谈JVM垃圾回收的前世今生的.说起垃圾回收(GC),大部分人把这项技术当做Java语言的伴生产物.事实上,GC的历史比Java久远,早在1960年Lisp这门 ...

  6. java jvm垃圾回收算法_深入理解JVM虚拟机2:JVM垃圾回收基本原理和算法

    本文转自互联网,侵删 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 喜欢的话麻烦点下Star哈 文章将同步到我的个人博客: www.how ...

  7. 简明易懂的JVM垃圾回收理解

    文章目录 写在前面 垃圾回收(GC)的概念 垃圾回收判断 垃圾回收区域 对象是否可以被回收 垃圾回收搜索根 GC Roots 安全点 Safepoint 垃圾回收的时刻 垃圾回收算法 标记-清除算法 ...

  8. 读书笔记之JVM垃圾回收

    前言 ​ 如果要问Java与其他编程语言最大的不同是什么,我第一个想到的一定就是Java所运行的JVM所自带的自动垃圾回收机制,以下是我学习JVM垃圾回收机制整理的笔记,希望能对读者有一些帮助. 哪些 ...

  9. 深入理解Java虚拟机——JVM垃圾回收机制和垃圾收集器详解

    一:概述 说起垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联系起来.在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,顾名思义,垃圾回收就是释 ...

  10. 【JAVA进阶】JVM第二篇- JVM 垃圾回收详解

    写在前面的话 脑子是个好东西,可惜的是一直没有搞懂脑子的内存删除机制是什么,所以啊,入行多年,零零散散的文章看了无数,却总是学习了很多也忘了很多. 痛定思痛的我决定从今天开始系统的梳理下知识架构,记录 ...

最新文章

  1. 西湖大学三位资深博导自述:我与我的第一位博士生
  2. mysql5.7.安装记录
  3. Coding:C++类定义实现部分成员函数
  4. jQuery学习笔记2
  5. leetcode79. 单词搜索(回溯算法)
  6. 解读边缘计算在7大领域的研究趋势和最新进展
  7. 【Kafka】Flink 消费 kafka 部分 分区 一直不提交 offset
  8. springboot 分页查询参数_精通SpringBoot--分页查询功能的实现
  9. 如何防止盗号 使用windows自带的 屏幕键盘 OSK
  10. java快捷键设置sop,今日工作总结|sop整理
  11. iphone7home键按压失灵_iPhone home键失灵怎么办 iPad home键失灵解决办法【详解】
  12. ElastcSearch的Mapping映射建立
  13. Python3 | UserWarning: findfont: Font family [‘SimHei‘] not found. Falling back to DejaVu Sans.
  14. c语言实现自动编译器,实现简易的C语言编译器(part 1)
  15. MATLAB 转置与共轭转置、在legend中输入数字、求频偏的小收获
  16. 复现《Cell》图表:双侧柱状图及坐标轴设置,ComplexHeatmap图例设置
  17. 神泣单机服务器维护,神泣单机版
  18. 面试时,你会问面试官哪些问题?
  19. 历年考研计算机专业国家线,历年工学考研国家线一览_考研国家线公布时间
  20. ThinkPhP关联查询setEagerlyType遇到的问题

热门文章

  1. Java基础-异常处理机制
  2. li标签行内元素高度及居中
  3. JavaScript算法(实例九)整数的置换 / 求s=a+aa+aaa+aaaa+aa...a的值 / 自守数
  4. TensorFlow2.0(八)--tf.function函数转换
  5. PyTorch入门(一)--PyTorch基础
  6. CSS Scrollbar (滚动条)
  7. 7-1 顶点的度 (15 分)
  8. java去掉重复字符_Java实现去掉字符串重复字母的方法示例
  9. java to oc_急急急!各位大神:一段JAVA代码转OC代码!
  10. html5新增标签与删除标签,HTML5新增/删除标签