深入拆解Java虚拟机
郑雨迪
Oracle 高级研究员,计算机博士
立即订阅
28017 人已学习
课程目录
已完结 39 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 为什么我们要学习Java虚拟机?
免费
模块一:Java虚拟机基本原理 (12讲)
01 | Java代码是怎么运行的?
02 | Java的基本类型
03 | Java虚拟机是如何加载Java类的?
04 | JVM是如何执行方法调用的?(上)
05 | JVM是如何执行方法调用的?(下)
06 | JVM是如何处理异常的?
07 | JVM是如何实现反射的?
08 | JVM是怎么实现invokedynamic的?(上)
09 | JVM是怎么实现invokedynamic的?(下)
10 | Java对象的内存布局
11 | 垃圾回收(上)
12 | 垃圾回收(下)
模块二:高效编译 (12讲)
【工具篇】 常用工具介绍
13 | Java内存模型
14 | Java虚拟机是怎么实现synchronized的?
15 | Java语法糖与Java编译器
16 | 即时编译(上)
17 | 即时编译(下)
18 | 即时编译器的中间表达形式
19 | Java字节码(基础篇)
20 | 方法内联(上)
21 | 方法内联(下)
22 | HotSpot虚拟机的intrinsic
23 | 逃逸分析
模块三:代码优化 (10讲)
24 | 字段访问相关优化
25 | 循环优化
26 | 向量化
27 | 注解处理器
28 | 基准测试框架JMH(上)
29 | 基准测试框架JMH(下)
30 | Java虚拟机的监控及诊断工具(命令行篇)
31 | Java虚拟机的监控及诊断工具(GUI篇)
32 | JNI的运行机制
33 | Java Agent与字节码注入
模块四:黑科技 (3讲)
34 | Graal:用Java编译Java
35 | Truffle:语言实现框架
36 | SubstrateVM:AOT编译框架
尾声 (1讲)
尾声 | 道阻且长,努力加餐
深入拆解Java虚拟机
登录|注册

11 | 垃圾回收(上)

郑雨迪 2018-08-15
你应该听说过这么一句话:免费的其实是最贵的。
Java 虚拟机的自动内存管理,将原本需要由开发人员手动回收的内存,交给垃圾回收器来自动回收。不过既然是自动机制,肯定没法做到像手动回收那般精准高效 [1] ,而且还会带来不少与垃圾回收实现相关的问题。
接下来的两篇,我们会深入探索 Java 虚拟机中的垃圾回收器。今天这一篇,我们来回顾一下垃圾回收的基础知识。

引用计数法与可达性分析

垃圾回收,顾名思义,便是将已经分配出去的,但却不再使用的内存回收回来,以便能够再次分配。在 Java 虚拟机的语境下,垃圾指的是死亡的对象所占据的堆空间。这里便涉及了一个关键的问题:如何辨别一个对象是存是亡?
我们先来讲一种古老的辨别方法:引用计数法(reference counting)。它的做法是为每个对象添加一个引用计数器,用来统计指向该对象的引用个数。一旦某个对象的引用计数器为 0,则说明该对象已经死亡,便可以被回收了。
它的具体实现是这样子的:如果有一个引用,被赋值为某一对象,那么将该对象的引用计数器 +1。如果一个指向某一对象的引用,被赋值为其他值,那么将该对象的引用计数器 -1。也就是说,我们需要截获所有的引用更新操作,并且相应地增减目标对象的引用计数器。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《深入拆解Java虚拟机》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(57)

  • godtrue 置顶
    非常感谢,此篇可用通俗易懂来形容,其他同学问的问题也很棒!

    小结:

    1:垃圾回收-核心工作就是回收垃圾,哪关键点回来了。什么是垃圾?这个垃圾需要分类嘛?怎么定位垃圾?怎么回收垃圾?回收垃圾的方法都有哪些?他们都有什么优缺点?另外,就是我们为什么要学习垃圾回收?

    2:站在JVM的视角来看
    垃圾-就是无用对象所占用的堆内存空间
    貌似不需要垃圾分类,识别垃圾并回收就行
    定位垃圾,是垃圾回收的关键点

    晚安💤,明天继续写
    2018-08-16
    8
  • 旭东 置顶
    赞,这种循序渐进的讲法,不知道了怎么工作,还知道了为啥要设计成这样,Why和what都和谐的在一起讲了
    2018-08-28
    1
  • 茶底
    老师下一期能讲一下g1算法吗。讲深一点😁
    2018-08-15
    24
  • godtrue
    非常感谢,此篇可用通俗易懂来形容,其他同学问的问题也很棒!

    小结:

    1:垃圾回收-核心工作就是回收垃圾,哪关键点回来了。什么是垃圾?这个垃圾需要分类嘛?怎么定位垃圾?怎么回收垃圾?回收垃圾的方法都有哪些?他们都有什么优缺点?另外,就是我们为什么要学习垃圾回收?

    2:站在JVM的视角来看

    垃圾-就是无用对象所占用的堆内存空间

    垃圾分类-貌似不需要垃圾分类,识别垃圾并回收就行

    定位垃圾-是垃圾回收的关键点,无用的对象占用的堆空间即是垃圾,那就需要先定位无用的对象,这里的无用是不再使用的意思,咋判断呢?文中介绍了两种方法,计数法和标记法(祥看原文)核心在于能定位出无用的对象,后出现的方法往往比早出现的更好一点,这里也一样,标记法能解决计数法,解决不了的循环引用不能回收的问题,但是也存在其他的问题,误报和漏报的问题,误报浪费点垃圾回收的机会浪费点空间,漏报在多线程并发工作时可能会死JVM的,所以,比较严重,所以,JVM采用了简单粗暴的stop-the-world的方式来对待,所以,老年代的回收有卡顿的现象

    怎么回收垃圾-定位出垃圾,回收就是一个简单的事情了,当然也非常关键,把要回收的堆内存空间标记为可继续使用就行,下次有新对象能在此空间创建就行

    回收垃圾的方法-文中介绍了三种,清除、压缩、复制

    清除法-简单,但易产生碎片,可能总空间够但分配不了的问题
    压缩法-能解决清除法的问题,但是复杂且耗性能
    复制法-折衷一些,但是空间利用率低,总之,各有千秋

    为什么要学-这个最容易,因为面试需要、装逼需要、升职加薪需要、人类天生好奇、还有免于被鄙视及可以鄙视其他人

    作者回复: 赞!

    2018-08-17
    2
    19
  • suynan
    安全点的这个地方,看得我是一脸懵逼
    2019-03-06
    10
  • Geek_488a8e
    误报和漏报,我觉得可惜这样理解,垃圾回收是先标记活的对象,后回收死的对象,那么如果标记好后,其它线程产生了垃圾,即将活的变死了,这种内存是不会释放的。另外,如果这时产生了新对象,由于没被标记为活的,所以被释放了,这就危险了
    2018-08-31
    10
  • godtrue
    疑问❓
    1:JVM的stop-the-world机制非常不友好,有哪些解决之道?原理是什么?
    2:压测时出现频繁的gc容易理解,但是有时出现毛刺是因为什么呢?
    3:fullgc有卡顿,对性能很不利,怎么避免呢?

    作者回复: 1. 采用并行GC可以减少需要STW的时间。它们会在即时编译器生成的代码中加入写屏障或者读屏障。

    2. Y轴应该是时间,那毛刺就是长暂停。一般Full GC就会造成长暂停。

    3. 通过调整新生代大小,使对象在其生命周期内都待在新生代中。这样一来,Minor GC时就可以收集完这些短命对象了。

    2018-08-17
    2
    8
  • Leon Wong
    老师你好,例子里的foo方法中的for循环,其中i变量类型我从int型改成long型后,长暂停的现象不存在了,请问是为何?

    作者回复: 这是C2一个诡异的地方。

    for (int i=start; i<limit; i++) {..}

    对于int类型的循环变量i,如果满足 1) 基于该循环变量的循环出口只有一个,即i < limit,2) 循环变量随着迭代的增量为常数,例子中i++即增量为1,以及循环变量的上限(当增量为负数时则是下限)为循环无关的,即limit应是循环无关,那么C2会将其判断成计数循环(counted loop),然后默认不插入safepoint。

    而对于long类型的循环变量,C2直接识别为非计数循环,需要插入safepoint。

    2018-09-11
    6
  • 彩色的沙漠
    @正是那朵玫瑰老师有几个不明白的地方,误报和漏报不太明白:
    1、假设A引用开始指向A1对象:A------>A1,按老师说的误报就是将引用A指向null:A------>null,那么此时A1对象不是没有引用了,不就可以垃圾回收了么,为什么会错过垃圾回收的机会呢?
    2、漏报,是将A引用指向一个未被访问的对象假设对象为B:A----->B,此时A引用原来指向的对象应该没有引用了吧,为什么会垃圾回收器可能会回收事实上仍被引用的对象呢?

    2018-08-15

     作者回复

    这里指的是,GC已经标记完成,然后其他线程进行修改的情况(也是并发GC所要解决的问题)。

    当GC标记完成,还未开始回收时,你更新了其中一个引用,使之指向null,那么原来指向的对象本可以被回收的。

    如果指向一个新的对象,这个对象可没有被标记为不能回收,垃圾回收器就直接给回收掉了

    老师我也有和@正是那朵玫瑰一样的问题,看了老师的讲解,还是不太明白。GC标记完成,那GC标记的是引用还是具体的堆空间对象。如果标记的具体的堆空间对象,并不会造成GC并发问题,误报和漏报,改变的是引用关系。请老师解答,谢谢!
    2018-08-15
    5
  • Jussi Lee
    一、垃圾回收算法
          1、引用计数法(文中已经介绍,主要的缺点是无法处理循环引用;在每次引用的产生和消除的时候,会伴随着一个加法或者减法的操作,对性能有一定的影响)
          2、标记清除法(从根节点出发开始所有可达的对象,未被标记的就是垃圾对象。主要缺点是产生空间碎片)
          3、复制算法(将原空间分为两块,每次使用其中一块,在垃圾回收时,进行复制,然后转换使用的内存空间。主要的缺点是将系统的内存折半。主要适用于存活对象少,垃圾对象多的情况下)
          4、标记压缩法(从根出发对所有可达对象进行一次标记,然后进行压缩。最后进行清理)
          5、分代算法(每一种垃圾回收算法都有其优缺点。分代算法是根据对象的特点分成几块,新建的对象放入新生代区域,当一个对象经历了几次复制后还存活则放入老年代。老年代因为对象存活率高复制算法不适用,因此采取标记清除或者标记压缩)
          6、分区算法(把堆空间划分为连续的不同小区间。降低了GC产生的影响)
    2018-09-29
    4
  • same old love
    我有个疑问,就是JVM回收掉对象以后,存活下来对象的内存地址值会不会改变
    2019-06-17
    3
  • 正是那朵玫瑰
    老师有几个不明白的地方,误报和漏报不太明白:
    1、假设A引用开始指向A1对象:A------>A1,按老师说的误报就是将引用A指向null:A------>null,那么此时A1对象不是没有引用了,不就可以垃圾回收了么,为什么会错过垃圾回收的机会呢?
    2、漏报,是将A引用指向一个未被访问的对象假设对象为B:A----->B,此时A引用原来指向的对象应该没有引用了吧,为什么会垃圾回收器可能会回收事实上仍被引用的对象呢?

    作者回复: 这里指的是,GC已经标记完成,然后其他线程进行修改的情况(也是并发GC所要解决的问题)。

    当GC标记完成,还未开始回收时,你更新了其中一个引用,使之指向null,那么原来指向的对象本可以被回收的。

    如果指向一个新的对象,这个对象可没有被标记为不能回收,垃圾回收器就直接给回收掉了

    2018-08-15
    2
  • no13bus
    昨天看书正好看到这章节,真的不错
    2018-08-15
    2
  • 茶底
    老师下一期能讲一下g1算法吗。讲深一点😁
    2018-08-15
    2
  •  素丶  
    结合 Rx 的回答容易帮助理解。
    https://www.zhihu.com/question/53613423/answer/135743258
    2019-08-09
    1
  • jiaobuchongจุ๊บ
    1、文中所说的误报和漏报是不是说反了啊,
         并发环境下标记完后,线程将引用改成 null,导致损失了部分垃圾回收的机会,这是属于漏报吧?
         已标记,然后将引用设置为未被访问过的对象,导致回收了仍被引用的内存,这个属于误报吧?
    2、在标记的过程中,是不是只需要记录存活的对象就行,不用标记垃圾对象,后续在执行回收算法的时候,也只是在操作已经标记的存活的对象?
    2019-02-17
    1
  • life is short, enjoy mor...
    老师,我心中有一个疑惑。
    压缩算法是不是也用到了复制呢?因为我觉得在压缩的过程中,也需要把存活的内存进行转移,而转移也就是复制吧?
    麻烦老师给回答一下~

    作者回复: 确实是需要复制数据,这样起名主要是为了区分复制到同一个区域中(需要复杂的算法保证引用能够正确更新),还是复制到另一个区域中(可以复制完后统一更新引用)。

    2018-10-16
    1
  • 浪迹江湖
    突发奇想:如果 GC 将引用计数算法和可达性分析算法结合起来使用会怎样?

    循环引用毕竟是少数,如果先用引用计数算法回收掉大部分对象,再对剩余的小部分对象采用可达性分析算法解决循环引用问题。可能比只使用可达性分析算法带来更好的回收效率。

    作者回复: 赞想法!不过我认为没有达到更好的回收效率,因为垃圾回收标记的是非垃圾,剩余没有标记的对象是垃圾。用引用计数法清理后,可达性分析仍需遍历所有活着的对象。

    但是可以将引用计数做成minor minor GC,只有当引用计数回收不了垃圾时,再触发可达性分析。感兴趣的话可以深入探索一下业界其他非Java runtime的垃圾回收算法。

    2018-09-26
    1
  • 杨春鹏
    老师,想问个题外题:
    关于安全点的选择,有一处为JNI执行本地方法。那么,既然java是跨平台的语言,可是它又调用本地方法,本地方法用c实现的,这不就破坏了其平台无关性吗?

    作者回复: 确实。一般需要用C实现的代码逻辑都是Java层面无法实现的了,没有办法才选择牺牲平台无关性。

    2018-08-19
    1
  • 黑崽
    第二,即时编译器生成的机器码打乱了原本栈桢上的对象分布状况。没明白这个原因。第一个原因中解释,只要去访问一个内存地址就可以知道是不是要暂停了,那我只有判断完暂停以后再去恢复寄存器中状态不就可以了?反正只有一次,这个打乱不打乱有什么区别呢?

    作者回复: 在GC时,我们需要知道哪个寄存器,以及哪个栈内存空间存放了指向对象的引用。这个信息需要记录下来。

    2018-08-19
    1
收起评论
57
返回
顶部