这篇承接上一篇 《Java的内存 - 内存模型》,分析内存回收相关的知识点。 垃圾回收包含两个步骤,①标记哪些内存是垃圾 ②回收内存。下面分别说这两个步骤有哪些算法:

1. 垃圾标记

1.1 引用计数算法

没有哪一种 JVM 是使用「引用计数」作为垃圾回收算法的,但这种算法又很经典,所以介绍一下。

工作方式: 堆中每一个对象都有一个引用计数器。创建并初始化赋值后,引用计数置为1,每多一次引用,引用计数+1,每有一个引用失效(出作用域 或者 被设置为其他值)时,引用计数-1。引用计数 == 0 的对象可以被回收。

优点: 判定对象是否需要回收的效率高,不需要额外的线程做 GC 的工作,也不会暂停应用。

缺点: 无法检测循环依赖。由于需要实时计数,增加了程序执行时的开销。

应用实例: Object-C 的 ARC 模式。


1.2 根搜索算法

根搜索算法是 Java 虚拟机主流的找垃圾算法。

首先要知道根集和finalize()相关的东西:

根集: 根集是肯定不需要回收的对象的引用。它包含: 1. Java栈 和 Native栈 的本地变量表中引用的对象; 2. 方法区中的常量和静态变量引用的对象; 3. 活跃的线程对象等。

关于 finalize() 方法: finalize() 方法最多只会被 垃圾收集器 执行一次(不包括开发者主动调用)。已经被垃圾收集器执行过一次后,不会再执行第二次。如果一个类没有重写 finalize() 方法,垃圾回收器不会执行其对象的 finalize() 方法。

根搜索算法会有两次标记,第一次标记将需要执行 finalize() 的对象放入 F-Queue 中,等待执行 finalize() 方法;第二次再判断执行完 finalize() 的对象是否依然不可达,并最终确定哪些对象是垃圾。 finalize() 方法是在一个低优先级的线程中执行的。

工作步骤如下:

第一步:获取不可达对象 1. 暂停整个应用(Stop The World); 2. 生成根集(GC Roots); 3. 从根集出发,找出根集中的对象引用的其他对象,并依次沿引用方向遍历,生成引用链; 4. 根据引用链获取所有不可达的对象;

第二步:垃圾的自我救赎 1. 判断这些不可达对象是否需要执行 finalize() 方法; 2. 如果不需要,直接标记为可回收对象,跳过后续步骤;如果需要,将对象加入到 F-Queue 中,等待执行 finalize() 方法; 3. 如果在 finalize() 方法中,其他对象又持有了该对象,那么该对象又变为可达对象了。

第三步:再次获取不可达对象 1. F-Queue队列执行完后,再次判断执行完 finalize() 方法的这些对象是否可达; 2. 如果仍然不可达,标记为可回收对象。


2.2 垃圾收集

关于垃圾回收,参考 Oracle 官网 《HotSpot 虚拟机内存管理白皮书》。

垃圾回收确保回收的对象必然是不可达对象,但是不确保所有的不可达对象都会被回收。

垃圾收集算法主要有3种: 标记清除算法(Mark-Sweep) 、标记压缩算法(Mark-Sweep-Compact)、复制算法(Copying)。在这3种的基础上派生出其它算法: 1. 分代回收:考虑算法和内存分配的特点,对堆上不同的分代使用不同的回收算法; 2. 并行回收(Parallel)和 串行回收(Serial): 考虑在内存回收时,是一个线程在回收,还是多个线程同时回收; 3. 并发回收 和 Stop-The-World:考虑在内存回收时,是否一边执行应用一边回收,还是完全暂停整个应用;

以下是这些算法的具体信息:


2.2.1 标记清除算法

工作方式: 1. 内部维护了一个空闲内存表,用来记录可分配内存的地址和大小; 2. 工作时,先将标记为可释放的对象的内存释放,然后在空闲内存表中更新可分配内存信息。

优点: 速度快。快的原因,相比后面的标记整理算法,是不需要移动内存。

缺点: 1. 会产生大量的内存碎片; 2. 维护一个空闲列表有一定的额外开销; 3. 分配新内存时,需要遍历空闲列表找到合适的内存块。


2.2.2 标记整理算法

工作方式: 1. 将所有活跃的对象,依次移动到内存的一端; 2. 移动完毕后,清理边界之外的内存。

优点: 1. 不会产生内存碎片; 2. 只需要记录内存末尾的指针,新内存分配时可以立即分配;

缺点: 1. 由于需要移动内存,暂停应用的时间会延长。


2.2.3 复制算法

工作方式: 1. 将堆内存分为两块相同的区域; 2. 内存分配时,只在其中一块内存分配; 3. 当内存不足以分配时,将所有活跃的对象依次复制到另一块区域; 4. 一次性清理掉旧的内存区域。

优点: 1. 标记和复制可以同时进行; 2. 效率高,清理内存时是对一整块内存进行操作; 3. 不会产生内存碎片。

缺点: 1. 可分配的堆内存减为一半了; 1. 由于需要移动内存,暂停应用的时间会延长。

特点: 该算法的耗时,只跟活跃对象的数量有关,和这个算法管理的堆空间总大小无关。


2.2.3 分代回收

由于不同对象的生命周期是不一样的,因此可以对不同生命周期的对象采取不同的收集方式,以提高回收效率。

不分区有什么缺点: 如果不分区,GC是对整个堆区进行可达性分析、内存移动等,回收会很耗时。

如何划分内存区域: 由于大部分对象的生命周期很短,只有少部分对象会存活较长时间。所以基于它们的生命周期分代是个合适的选择。HotSpot 等虚拟机都把堆区分为 年轻代 和 老年代。

分代是如何减少可达性分析的: 对年轻代做可达性分析时,如果还要遍历老年代,那就没有减少可达性分析的时间。 但是。如果不遍历其它分代,如何知道一个年轻代的对象是否被老年代持有呢?这就产生了跨代引用的问题。

为了解决问题,引入了「跨代引用是 GC Root」的解决办法:如果老年代的 Young 对象,引用了年轻代的 Old 对象,在对年轻代进行可达性分析时,Young 对象算作 GC Root。这样就不用遍历其它分代了。 分代回收算法需要有一个表,用来记录所有的跨代引用,很耗内存。HotSpot 使用 CardTable 记录老年代对年轻代的引用。把老年代按照 4KB 的大小分块,每一块对应在 CardTable 中都是1 bit。当值为1时,表示这4KB 的内存中有对年轻代的引用,需要加入到 GC Roots 中。

这种解决办法也会有问题:如果A对象没有被其它对象引用,实际上A、B都应该被回收,但却把B当作GC Root了。也就是部分不可达对象没有被回收。

如何选择合适的算法: 1. 对于年轻代的对象,它们数量多、生命周期短,且大部分对象都是要回收的,所以需要速度更快的垃圾回收算法。Copying 算法的耗时,只跟堆内活跃对象的数量有关,跟堆的大小无关,所以特别适合用于年轻代的回收。 2. 对于老年代的对象,他们占用内存大,不能使用复制算法,并且需要避免内存碎片,所以使用 标记整理算法。

在回收年轻代时,可达性分析只分析年轻代。在回收老年代时,是对整个堆区做可达性分析。

java 全局变量 内存不回收_Java的内存 - 内存回收相关推荐

  1. java 内存分配实例_java学习(四) —— 内存分配浅析

    前言 java中有很多类型的变量.静态变量.全局变量及对象等,这些变量在java运行的时候到底是如何分配内存的呢?接下来有必要对此进行一些探究. 基本知识概念: (1)寄存器:最快的存储区, 由编译器 ...

  2. java 内存模型面试_Java面试- JVM 内存模型讲解

    经常有人会有这么一个疑惑,难道 Java 开发就一定要懂得 JVM 的原理吗?我不懂 JVM ,但我照样可以开发.确实,但如果懂得了 JVM ,可以让你在技术的这条路上走的更远一些. JVM 的重要性 ...

  3. java 内存分配管理_JAVA实验操作系统内存管理-最优分配,最先分配,最坏分配算法...

    package Memory; import java.util.ArrayList; import java.util.Iterator; import java.util.Scanner; pub ...

  4. java 堆内存和栈内存的区别_java中栈内存和堆内存有什么区别

    栈内存和堆内存的区别: 1.栈内存用来存放基本类型的变量和引用变量,堆内存用来存储java中的对象,无论是成员变量,局部变量,还是类变量,他们指向的对象都存储在堆内存中. (视频教程推荐:java视频 ...

  5. java String如何回收_java中的垃圾回收

    原文-http://blog.csdn.net/zsuguangh/article/details/6429592 1. 垃圾回收的意义 在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确 ...

  6. java string 内存写了_Java String的内存机制

    JVM运行时,会将内存分为两个部分:堆和栈.堆中存放的是创建的对象,而栈中存放的方法调用过程的局部变量或引用.而设计Java字符串对象内存实现的时候,在堆中又开辟了一块很小的内存,称之为字符串常量池, ...

  7. java 手动垃圾回收_java如何进行垃圾回收的

    转:http://blog.csdn.net/yakihappy/article/details/3979944 垃圾收集的目的在于清除不再使用的对象.gc通过确定对象是否被活动对象引用来确定是否收集 ...

  8. java 线程池 资源回收_JAVA线程池资源回收的问题

    最近项目中为了提高用户体验度,前台创建任务后台任务,用多线程来跑. 现在的场景:后台定时任务管理这两个线程池,一个最大线程数10个,一个最大线程数15.应用部署之后,不超过5个小时,服务器负载高,内存 ...

  9. java 线程回收_JAVA线程池资源回收的问题

    最近项目中为了提高用户体验度,前台创建任务后台任务,用多线程来跑. 现在的场景:后台定时任务管理这两个线程池,一个最大线程数10个,一个最大线程数15.应用部署之后,不超过5个小时,服务器负载高,内存 ...

  10. java 全局变量 局部变量的区别_java中全局变量和局部变量的区别是什么?

    全局变量是编程术语中的一种,源自于变量之分.全局变量既可以是某对象函数创建,也可以是在本程序任何地方创建.全局变量是可以被本程序所有对象或函数引用. 局部变量(Local variables):在方法 ...

最新文章

  1. 林园投资体系+嘴巴经济+病从口入+老龄化
  2. WCF:如何将net.tcp协议寄宿到IIS
  3. 计算机二级和省考撞车了,事考、省考撞车?7月25日笔试,该怎么选?
  4. windows下安装Python-Whl文件
  5. P3586-[POI2015]LOG【线段树】
  6. 概率论与数理统计(二)选择题
  7. 第二届世界智能大会,看大咖眼中的智能时代
  8. 【渝粤教育】国家开放大学2018年秋季 0248-21T电工电子技术 参考试题
  9. css实现页面标签的跳转
  10. python 企业微信接口_python连接企业微信发送消息
  11. 不定式和动名词复合结构是什么
  12. 苹果自带输入法怎么换行_微信个性签名怎么弄成竖的?不仅可以竖着还可以加边框效果!...
  13. 【强化学习】Actor-Critic(演员-评论家)算法详解
  14. 理解:什么是接口,接口的概念
  15. 国际物流管理信息系统(LMIS)
  16. Linux 防火墙操作指令
  17. 计算机没法安装打印程序,无法安装打印机怎么办 无法安装打印机解决方法【图文教程】...
  18. 明日之后维尔市服务器找不到,明日之后:迁徙计划仅对“部分人”开启,果然没有想象中那么简单...
  19. android开发中dx.jar,Android Studio:无法加载dx.jar
  20. 英语学习(八)独立性从句之并列句

热门文章

  1. 强化学习(三) - Gym库介绍和使用,Markov决策程序实例,动态规划决策实例
  2. python中利用lxml模块解析xml文件报错XMLSyntaxError: Opening and ending tag mismatch
  3. ceph 部署单机集群
  4. windows 10 下部署WCF 一些细节
  5. 【牛客网】最长对称子串
  6. java中xxe漏洞修复方法
  7. 2018.11.12
  8. 数据分析之CE找数据大法
  9. 2017.4.18 静态代码分析工具sonarqube+sonar-runner的安装配置及使用
  10. TextView-- 测量文字宽度