在本篇中,作者大量篇幅介绍了当时较为流行的垃圾回收器,但现在Java 14都发布了,垃圾收集器也是有了很大的进步和发展,因此在此就不再对垃圾收集器进行详细的研究。但其基本的算法思想还是值得我们参考学习的。

概述

   第一篇笔记Java内存区域与内存溢出异常中讲到了,Java的内存划分可以分为由所有线程共享的Java堆和方法区,以及每个线程之间相互独立的程序计数器、本地方法栈、虚拟机栈。其中每个线程相互独立的部分,并不会给垃圾回收造成困难。因为随着方法、线程的结束,内存自然就释放了。因此我们垃圾收集关注的,都是Java堆和方法区这部分内存,因为这部分内存的回收和分配都是动态的。

对象存亡的判别

   在对垃圾回收之前,我们应该先判断哪些对象没有用应该被回收、哪些对象应该被保留。

引用计数算法

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

   这种判别方法简单易行,但是Java语言并没有使用这样的方法来判别对象,主要的原因是这种算法无法解决对象之间相互调用的情况。

   比如有这样的代码段:对象a和对象b之间相互引用,然后把他们全赋值为null,此时他们不可能再被访问,但是因为他们相互调用,引用计数器不为0,则无法释放对象。

  1. public class TestGC {
  2. public Object obj = null;
  3. public static void main(String[] args) {
  4. TestGC a = new TestGC();
  5. TestGC b = new TestGC();
  6. a.obj = b;
  7. b.obj = a;
  8. a = null;
  9. b = null;
  10. }
  11. }

根搜索算法

   为了解决上面提到的相互引用的问题,Java采用根搜索算法来判别对象是否存活。

   根搜索算法的基本思路是:通过一系列名为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到"GC Roots"没有任何引用链相连的时候,则证明该对象是不可用的。

   在Java中,可以作为"GC Roots"的对象包括:

   1、虚拟机栈(栈帧中的本地变量表)中的引用的对象。

   2、方法区中的类静态属性的引用对象。

   3、方法区中的常量引用的对象。

   4、本地方法栈中JNI(native方法)的引用对象。

   对于如下的图,即使Object4和object5有链接关系,但是因为没有跟"GC Roots"相连,所以仍然认为是不可用的对象。

方法区中的判别

   相对于堆中垃圾回收相比,方法区中的垃圾回收的性价比比较低,在堆中,尤其是在新生代中,常规垃圾回收一次可以回收70%~95%的空间。而永久代的垃圾收集效率远低于此。

   永久代的垃圾回收主要回收两个部分:废弃常量和无用的类。

   回收废弃常量很简单,只要判别没有对象引用此常量即可。而收集无用的类就要复杂许多。一般来说,类要满足如下三个条件才能看做无用的类:

   1、该类的所有实例都已经被回收,也就是Java堆中不存在任何该类的实例。

   2、加载该类的ClassLoader已经被回收。

   3、该类对应的java.lang.class对象没有在任何地方被引用。无法在任何地方通过反射访问该类的方法。

   在大量使用反射、动态代理的框架中,以及动态生成JSP的频繁自定义ClassLoader的场景,都需要虚拟机具有类卸载的功能,以保证永久代不会溢出。

垃圾收集算法

  1. 只是对于算法思路的讲解,而忽略具体的实现细节。

标记-清除算法

   标记-清除算法是最基础的算法,它首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

   这个算法的好处是,实现简单,但是它有两个严重的问题:1、效率低下,标记和回收的效率都不高。2、空间问题,因为标记回收会造成大量的不连续的内存碎片,当一个较大的对象需要分配时,往往会因为找不到对应连续的存储空间而多次执行垃圾收集工作。

   虽然标记-清除算法有这样的缺点,但其他的算法大多是在此算法的思路上进行优化,改进其存在的问题。

   标记-清除算法的示意图如下:

复制算法

   为了解决效率问题,复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块内存用完了,就将还存活的对象复制到另外一块上面,然后把已使用过的内存空间全部清理。这样做的好处是不用考虑内存碎片问题,只要按顺序分配内存即可,而且每次只要对一块内存进行清理。但缺点就是使实际可用的内存空间减小。

   复制算法的示意图如下:



   但是,实际根据研究表明,新生代中98%的对象都是创建后存在很少一段时间就不可用了,因此不需要按照1:1的情况进行划分。因此这个算法的实际思路其实就是:进行一遍标记后,让还存活的保留下来,然后顺序安置到另一个区域,再重新使用该空间进行接下来变量的存储。

   在作者写作时,表示商业虚拟机都采用此复制算法进行回收新生代。而内存划分为1个eden和2个survivor,每次使用eden和一个survivor来存储,另一个survivor来存储保存下来的对象。通常eden和survivor的大小比例是8:1.

   但如果有超过10%的对象存活,就需要依赖其他内存(如老年代)来进行分配担保。

标记-整理算法

   该算法与标记-清除算法相比,不同就是此算法会将仍然存在的对象往前移动,从而解决了大量内存碎片的问题。

   其示意图为:

分代收集算法

   该算法没有新的思想,只是按照对象存活周期将内存划分为几块,然后不同的内存划分对应于不同时期的对象,采用不同的垃圾收集算法。

内存分配与回收策略

   内存分配有几条普遍的规则:

对象优先在Eden区分配。

   大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够空间进行分配的时候,虚拟机将发起一次Minor GC.

大对象直接进入老年代。

   大对象就是需要大量连续内存空间的Java对象。像很长的字符串、数组之类。

长期存活的对象将进入老年代

   虚拟机给每个对象定义了一个对象年龄计数器,对象每次在Minor GC下存活一次则加1.当达到阈值时,可以晋升为老年代。

动态对象年龄判定

   并不是所有情况下,都必须要达到阈值才能晋升为老年代,如果Survivor中对象过多,也可以提前进入老年代。

空间分配担保

   新生代使用复制算法时,并不能保证每次存活的对象都小于Survivor空间大小,如果超出了的话,就需要借用老年代的空间进行对象的存储。

垃圾收集器与内存分配策略——深入理解Java虚拟机 笔记二的更多相关文章

  1. 深入理解java虚拟机----->垃圾收集器与内存分配策略(下)

    1.  前言 内存分配与回收策略 JVM堆的结构分析(新生代.老年代.永久代) 对象优先在Eden分配 大对象直接进入老年代 长期存活的对象将进入老年代 动态对象年龄判定 空间分配担保  2.  垃圾 ...

  2. 《深入理解Java虚拟机》(三)垃圾收集器与内存分配策略

    垃圾收集器与内存分配策略 详解 3.1 概述 本文参考的是周志明的 <深入理解Java虚拟机>第三章 ,为了整理思路,简单记录一下,方便后期查阅. 3.2 对象已死吗 在垃圾收集器进行回收 ...

  3. 《深入理解java虚拟机》第三章 垃圾收集器与内存分配策略

    第三章 垃圾收集器与内存分配策略 3.1 概述 哪些内存需要回收 何时回收 如何回收 程序计数器.虚拟机栈.本地方法栈3个区域随线程而生灭. java堆和方法区的内存需要回收.   3.2 对象已死吗 ...

  4. 深入理解java虚拟机_第三章(上)----->垃圾收集器与内存分配策略

    1.  前言 这一版块内容比较多,分为两篇文章来做笔记.本文讲述上半部分垃圾收集部分;下一篇文章写内存分配部分. 概述 对象已死吗? 引用技术算法 可达性分析算法 再谈引用 两次标记 回收方法区 2. ...

  5. java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项 ...

  6. jvm系列 (二) ---垃圾收集器与内存分配策略

    垃圾收集器与内存分配策略 前言:本文基于<深入java虚拟机>再加上个人的理解以及其他相关资料,对内容进行整理浓缩总结.本文中的图来自网络,感谢图的作者.如果有不正确的地方,欢迎指出. 目 ...

  7. 《深入java虚拟机》读书笔记之垃圾收集器与内存分配策略

    前言 该读书笔记用于记录在学习<深入理解Java虚拟机--JVM高级特性与最佳实践>一书中的一些重要知识点,对其中的部分内容进行归纳,或者是对其中不明白的地方做一些注释.主要是方便之后进行 ...

  8. Java虚拟机垃圾收集器与内存分配策略

    Java虚拟机垃圾收集器与内存分配策略 概述 那些内存须要回收,什么时候回收.怎样回收是GC须要完毕的3件事情. 程序计数器.虚拟机栈与本地方法栈这三个区域都是线程私有的,内存的分配与回收都具有确定性 ...

  9. JVM性能优化系列-(2) 垃圾收集器与内存分配策略

    2. 垃圾收集器与内存分配策略 垃圾收集(Garbage Collection, GC)是JVM实现里非常重要的一环,JVM成熟的内存动态分配与回收技术使Java(当然还有其他运行在JVM上的语言,如 ...

随机推荐

  1. Ansible playbook 编程

    Ansible playbook 编程详解与各种小案例 主机规划 添加用户账号 说明: 1. 运维人员使用的登录账号: 2. 所有的业务都放在 /app/ 下「yun用户的家目录」,避免业务数据乱放: ...

  2. 链表数据-PHP的实现

    首先,链表数据的结构是: 其次,链表数据的结构特点: 随后,填充链表结构: 链表结构的数据,从链表尾部塞入数据. 最后,删除链表结构:

  3. java学习(第四篇)数组

    一.一维数组 1.声明,分配内存 int[] a=new int[10]; 数组元素的数据类型 [] 数组名=new 类型 [数组元素个数]: 2.初始化 int[] a=new int[] {1,2 ...

  4. java在指定区间内生成随机数

    Random对象生成随机数 首先需要导入包含Random的包 import java.util.Random; nextInt(int)方法将生成0~参数之间的随机整数但不包括参数. 例如生成0~99 ...

  5. 标准SQL语句大全【持续更新】(navicat12版亲测有效)

    提示:用ctrl+F快速查找相关指令哦 -- 创建数据库 create database test_sql; -- 修改数据库名称(只有 sysadmin 和 dbcreator 固定服务器角色的成员 ...

  6. 常用的CSS小技巧

    实际开发过程中会遇到一些需要用CSS小技巧处理的布局问题,现在分享几个个人工作中遇到的小问题和解决方案. 1.inline元素间的空白间隙 这里要介绍一个神器font-size:0. 如果你写了个列表 ...

  7. Android Room SQLite持久层框架

    原文链接 前言 Android中提供了SQLite数据库进行数据的持久化 ,并提供了对应API访问数据库,而Room框架提供了SQLite数据访问抽象层,为高效的数据库访问层带来便捷 APP可以缓存用 ...

  8. SVN签出,回退

    2019独角兽企业重金招聘Python工程师标准>>> yum install -y subversion 安装SVN 签出代码 : [root@test svn]# svn che ...

  9. 网络流--最大流--POJ 1459 Power Network

    #include<cstdio> #include<cstring> #include<algorithm> #include<queue> #incl ...

  10. P4370 [Code+#4]组合数问题2

    题目要求当\(0\leq a\leq b\leq n\)时,\(k\)个\(\tbinom{b}{a}\)的和的最大值 观察杨辉三角形,可以发现,最大的\(\tbinom{b}{a}\),为\(\tb ...