通常,我们在写java程序的时候,似乎很少关注内存分配和垃圾回收的问题。因为,这部分工作,JVM已经帮我们自动实现了。

这样看起来,好像很美好,但是任何事情都有两面性。虽然JVM会自动的进行垃圾回收,但是,如果遇到有些问题,JVM自己也处理不了呢?

因此,我们需要了解一下JVM垃圾回收是怎样运作的,这样才能在遇到问题的时候,有的放矢。所以,今天就来聊一聊JVM的垃圾回收吧。

首先,思考一下,为什么需要进行垃圾回收?

我们知道,在创建对象的时候,Java会把对象的内容放到堆中。随着时间的推移,堆中的对象肯定会越来越多,但是,堆的大小是有限制的。如果,我们不进行垃圾回收,也就是把无用的对象进行清除和回收,那么JVM将不堪重负,最终导致内存泄漏。

既然我们需要进行垃圾回收,那么,首先得知道什么是垃圾。

在垃圾收集器对堆内存进行回收前,会先判断哪些对象还在“存活”,哪些对象已经“死去”(即不可能再被任何途径使用的对象),这些“死去”的对象,就是我们需要进行回收的垃圾。

那么,通过什么方式去判定是否为垃圾呢?(即判定对象是否存活)

引用计数算法(已淘汰)

引用计数算法,是指给对象中添加一个引用计数器,每当有一个地方引用它时,计数器的值就加1,当引用失效时,计数器的值就减1。当计数器值为0时,该对象就会被回收。

可以说,引用计数算法的实现非常简单,判定效率也很高。但是,我们忽略了一个问题,在Java中,对象之间是可以互相循环引用的。如果,两个对象之间互相循环引用,那么就会导致,它们之间的引用计数都不为0(都在等待对方释放资源),因此,就无法通知垃圾收集器回收它们。

可达性分析算法

这个算法的思想就是,通过一系列被称为“GC Roots”的对象作为起点,然后向下搜索,所走过的路径被称为引用链。当一个对象到 GC Roots之间没有任何引用链时(即从GC Roots到该对象不可达),则证明该对象是不可用的。

这个算法解决了循环引用的问题,只要对象无法与GC Root之间建立直接或间接的连接,就会判定为可回收对象。

那么,什么对象可以作为GC Root呢?一般分为以下四种:

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

既然已经确定了哪些垃圾可以被回收,那么就需要垃圾收集器进行垃圾回收了,我们来了解一下几种比较常见的的垃圾收集算法。

标记清除算法

是最基础的一种收集算法,分为标记和清除两个阶段。首先,把需要回收的对象标记出来,然后再把他们清除掉。如上图所示,所有可回收的对象会变成未使用的一片区域。

标记清除算法逻辑清晰,易于操作。但是,我们可以看到,未使用的内存区块都不是连续的,因此,此算法会产生很多的内存碎片。这样,当一些较大的对象需要分配空间的时候,就找不到足够的连续内存来存储,因此会提前触发GC,同时也浪费了很多的内存空间(内存空间太小,导致不可用)。

复制算法

复制算法,是指把内存区域划分为大小相等的两块区域。每次只使用其中的一块,当这一块内存用完了,就把所有存活的对象复制到另一块上面,最后再把已使用过的内存空间一次清理掉。

这样,就可以保证内存区域的连续性,不会产生内存碎片,实现简单,运行高效。但是,这样的话只有使用原来一半的内存,代价也太高了。

标记整理算法

标记整理算法,标记过程和标记清除算法一样,但是后续不是进行清除,而是先整理,让所有存活的对象都向一端移动,然后再清除另一端的内存区域。

标记整理算法解决了标记清除算法产生内存碎片的问题,同时也解决了复制算法只能利用一半内存的问题,看似是非常的完美。但是,它却产生了另外一个问题。可以看到图中,内存的变动非常频繁,每次整理都有很多存活的对象内存地址发生改变。因此,它的效率会慢很多。

所以,现在一般用分代收集算法。在Java堆中,分为新生代和老年代,可以根据各个代的特点,选择最合适的收集算法。新生代中,每次垃圾收集都有大批对象死去,只有少量对象存活,就可以选择复制算法,只需要付出少量存活对象的复制成本即可。而老年代中,对象存活率高,没有额外空间对它进行分配担保,因此使用标记清除或者标记整理算法。

堆内存模型

Java堆是内存管理中最大的一块区域,也是垃圾回收的重点区域。堆分为新生代、老年代和永久代,新生代又分为Eden区和Survivor区,Survivor又分为S0和S1区。在JDK1.8之后把永久代移除了,而用元空间代替。(永久代使用的是堆内存,而元空间直接使用本机物理内存)

新生代中的对象98%都是朝生夕死的,因此把新生代分为较大的一块Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor(此处Survivor区也叫From区,另一块空的未使用的空间叫To区,From和To区是会交换的,保证空的总是To区)。

当Eden区没有足够的空间分配时,会进行一次Minor GC,Eden区大部分对象都被回收,而Eden区和From区存活的对象会放入到To区,然后From和To区进行交换。(如果To区空间不够,直接进入老年代)

以下几种情况会进入老年代。

1) 大对象

大对象就是指需要大量连续内存空间的对象,最典型的就是那种很长的字符串和数组。大对象会直接进入到老年代,这样做的目的主要是为了避免新生代发生大量的内存复制(大对象的复制成本较高)。

2)长期存活的对象

虚拟机给每个对象都定义了一个对象年龄计数器。每当进行一次Minor GC,年龄就增加1岁,当年龄超过一定值时(默认是15,可以通过参数配置),就进入到老年代。

3)动态对象年龄判断

虚拟机并不要求对象年龄一定要到达15岁才进入到老年代。如果Survivor空间中有某年龄相同的所有对象大小总和大于Survivor空间的一半,则年龄大于等于该年龄的对象就会直接进入老年代。

空间分配担保

在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果大于,那么Minor GC可以确保是安全的(因为,极端情况下,就算新生代所有对象都存活,也可以保证安全晋升到老年代)。否则,虚拟机会查看HandlePromotionFailure的值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小。如果大于,将尝试进行一次Minor GC(尽管有风险);如果小于或者HandlePromotionFailure设置不允许冒险,那么就先进行一次Full GC。

以上说的有风险,是因为取历次晋升到老年代对象的平均值这种方式只是经验值,并不能保证每次都能担保成功,如果担保成功还好,如果担保失败的话,依然需要进行Full GC。

尽管如此,我们最好还是打开HandlePromotionFailure开关,避免过多频繁的Full GC(因为Full GC的执行速度比Minor GC慢的多)。

JVM垃圾回收详解的更多相关文章

  1. Java虚拟机之垃圾回收详解一

    Java虚拟机之垃圾回收详解一 Java技术和JVM(Java虚拟机) 一.Java技术概述: Java是一门编程语言,是一种计算平台,是SUN公司于1995年首次发布.它是Java程序的技术基础,这 ...

  2. 基于Python对象引用、可变性和垃圾回收详解

    基于Python对象引用.可变性和垃圾回收详解 下面小编就为大家带来一篇基于Python对象引用.可变性和垃圾回收详解.小编觉得挺不错的,现在就分享给大家,也给大家做个参考. 变量不是盒子 在示例所示 ...

  3. python 垃圾回收详解

    原文:https://zhuanlan.zhihu.com/p/31150408 总纲 策略和垃圾回收系统工作内容 引用计数详解 标记-清除+分代收集 循环引用 编程应用-常见方法 ex 过程详解 使 ...

  4. JVM 垃圾回收器详解

    小结: 新生代    串行Serial            并行 Parallel(关注吞吐量)           并行ParNew 老年代    串行 Serial Old     并行Para ...

  5. C# using垃圾回收详解

    简介 定义一个范围,将在此范围之外释放一个或多个对象. 语法 using (Font font1 = new Font("Arial", 10.0f)) { } C# 语言参考 主 ...

  6. Python垃圾回收详解:引用计数+标记清理+分代回收

    Python采用的是引用计数机制为主,标记-清理和分代收集两种机制为辅的策略. 1.引用计数 python中一切皆对象,所以python底层计数结构地就可以抽象为: 引用计数结构体{ 引用计数; 引用 ...

  7. 深入JVM垃圾回收机制,值得你收藏

    JVM可以说是为了Java开发人员屏蔽了很多复杂性,让Java开发的变的更加简单,让开发人员更加关注业务而不必关心底层技术细节,这些复杂性包括内存管理,垃圾回收,跨平台等,今天我们主要看看JVM的垃圾 ...

  8. JVM垃圾回收算法及回收器详解

    引言 本文主要讲述JVM中几种常见的垃圾回收算法和相关的垃圾回收器,以及常见的和GC相关的性能调优参数. GC Roots 我们先来了解一下在Java中是如何判断一个对象的生死的,有些语言比如Pyth ...

  9. 深入理解JVM虚拟机3:垃圾回收器详解

    JVM GC基本原理与GC算法 Java的内存分配与回收全部由JVM垃圾回收进程自动完成.与C语言不同,Java开发者不需要自己编写代码实现垃圾回收.这是Java深受大家欢迎的众多特性之一,能够帮助程 ...

随机推荐

  1. 我是如何一步步裹挟老板从.net 转到 java 阵营的

    我是如何一步步裹挟老板从.net 转到 java 阵营的 仅记录从 .net(C#) 转到 java 的一些心路历程 时间点跨度 2016 — 2017 一.前 xx 公司同事群的一次聊天 前公司同事 ...

  2. 15.Python文本转化语音方法

    1.用pywin32模块来将文本转化为语音 通过pip install pywin32安装模块,pywin32是个万金油的模块,太多的场景使用到它,但在文本转语音上,它却是个青铜玩家,简单无脑但效果不 ...

  3. Python 任务自动化工具:nox 的配置与 API

    英文 | Configuration & API 出处 | nox 官方文档 译者 | 豌豆花下猫@Python猫 Github地址:https://github.com/chinesehua ...

  4. Activiti定时任务

    Activiti定时任务 作者:Jesai 傻逼一样的去坚持,就会有牛逼的结果 情景: 某公司有一个OA系统,审批环节是经理.有一天,经理出差了,然后下面突然有一份决定公司某个重大项目是否能顺利中标的 ...

  5. 洛谷 P5424 [USACO19OPEN]Snakes

    题目链接 题目描述 传说,数千年前圣帕特里克消灭了哞尔兰所有的蛇.然而,蛇们现在卷土重来了!圣帕特里克节是在每年的3月17日,所以Bessie要用彻底清除哞尔兰所有的蛇来纪念圣帕特里克. Bessie ...

  6. vue拦截器

    1.在路由添加 meta:{ requireAuth:true } 完整 { path: '/xx', name: 'xx', component: xx, meta:{ requireAuth:tr ...

  7. 异数OS-织梦师-PBFT(六) 走出区块链,加速破解PBFT

    . 异数OS-织梦师-PBFT(六) 走出区块链,加速破解PBFT 拜占庭 本文来自异数OS社区 github: https://github.com/yds086/HereticOS 异数OS社区Q ...

  8. 百度搜索关键词联想API JSONP使用实例

    许多搜索引擎都提供了关键词联想api,且大多数都是jsonp. Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)那获 ...

  9. 痞子衡嵌入式:语音处理工具pzh-speech诞生记(2)- 界面构建(wxFormBuilder3.8.0)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是语音处理工具pzh-py-speech诞生之界面构建. 之前痞子衡设计过一个串口调试助手pzh-py-com,也专门写过一篇关于其界面构 ...

  10. Python学习,第五课 - 列表、字典、元组操作

    本篇主要详细讲解Python中常用的列表.字典.元组相关的操作 一.列表 列表是我们最以后最常用的数据类型之一,通过列表可以对数据实现最方便的存储.修改等操作 通过下标获取元素 #先定义一个列表 le ...