对象存活判断

引用计数

在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可 能再被使用的

引用计数法的缺陷:

public class ReferenceCountingGC {
public Object instance = null;
private static final int _1MB = 1024 * 1024;
/**
* 这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否有回收过
*/
private byte[] bigSize = new byte[2 * _1MB];
public static void testGC() {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
// 假设在这行发生GC,objA和objB是否能被回收?
System.gc();
}
}

如果使用引用计数法objAobjB除互相引用外没有任何其他引用,但是无法被回收。

可达性分析

通过一些了GC Roots的根对象作为起点集,根据引用关系向下搜索,搜索过程所走过的路径称为引用链,如果某个对象和GC Roots之间没有任何引用链,则该对象不可达,需要被回收。

GC Roots 对象

  • 虚拟机栈(局部变量表)中引用的对象
  • 方法区中静态属性引用的对象
  • 方法区在常量引用的对象
  • 本地方法栈中Native方法引用的对象
  • Java虚拟机内部的引用
  • 被同步锁持有的对象
  • 等等

Java 引用

引用类型 描述
强引用 Object obj = new Object(); 垃圾回收器永远不会回收强引用对象
软引用 SoftReference; 系统发生内存溢出异常前,会回收软引用对象
弱引用 WeakReference; 无论内存是否足够,垃圾回收时都会回收弱引用对象
虚引用 PhantomReference; 该对象设置虚引用关联,唯一目的是该对象被回收时会收到一个系统通知

并发可达性分析

可达性分析在并发环境下存在的问题

问题1: 原本消亡的对象错误标记为存活对象(可容忍)
问题2: 原本存活的对象错误标记为消亡对象(不可容忍,导致程序出错)

可达性分析过程 —— 三色标记

  • 黑色节点: 对象已经被垃圾收集器访问过,且该对象的所有引用均已扫描
  • 灰色节点: 对象已经被垃圾收集器访问过,但该对象至少还有一个引用未被扫描
  • 白色节点: 对象尚未被垃圾收集器访问,可达性分析结束后,节点仍然为白色,则标识该对象不可达

问题2出现的原因: 同时满足以下两个条件

  • 并发标记期间程序增加一条或多条黑色对象到白色对象的引用
  • 并发标记期间删除了所有灰色对象到该白色对象的直接或间接引用,所以该对象无法被垃圾收集器访问到,导致误标记为消亡对象

因为黑色对象已经不会再被访问,所以新增的引用无法生效;同时删除了所有其他灰色对象到该白色对象的直接或间接引用,所以该对象无法从其他引用链访问到,导致存活对象被错误标记为消亡对象。

解决方案

  • 增量更新: 当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。如CMS垃圾回收器。
  • 原始快照: 当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。如G1垃圾回收器

垃圾回收算法

标记-清除算法

主要分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象

存在的缺陷:

  1. 执行效率不稳定,随着标记对象数量的增长而降低
  2. 内存空间碎片化问题,标记-清除会产生大量不连续的内存碎片

标记-复制算法

将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。

当某一块内存快用完了,就将存活的对象复制到另一块内存上,然后把原来的内存空间直接清理即可。

相较于标记-清除算法的优点:

  1. 每次针对一个半区进行清理,不会产生碎片化的内存空间

存在的缺陷:

  1. 如果内存中多数对象都是存活对象,则会产生大量的内存间复制开销
  2. 将可用内存缩小为原来的一半,空间利用率低

现代Java虚拟机大多采用标记-复制算法回收新生代,因为新生代中只有极少数对象会存活。

标记-整理算法

首先标记出所有需要回收的对象;让所有存活对象向内存空间的一侧移动,最后清理掉边界以外的内存。

存在的缺陷:

  1. 移动存活对象并更新所有引用这些对象的地方是极为负重的操作,而且这种对象移动操作必须暂停用户程序才能进行(Stop The World)

垃圾回收器

CMS收集器

Concurrent Mark Sweep(CMS) 收集器是一种以获取最短回收停顿时间(STW)为目标的收集器。该回收器基于标记-清除算法实现。

CMS收集器工作过程

  1. 初始标记(STW): 标记 GC Roots 能直接关联到的对象,速度很快
  2. 并发标记: 从 GC Roots 直接关联对象开始遍历整个对象图,不需要停顿用户线程
  3. 重新标记(STW): 修正并发标记,增量更新
  4. 并发清除: 清理掉所有消亡对象

CMS收集器由于使用并发-清除算法回收,会产生大量的内存空间碎片,可用暂时容忍;当内存空间的碎片化程度影响到对象分配时,再采用一次标记-整理算法,以获得规整的内存空间。

G1收集器

Grabage First(G1) 收集器开创了面向局部收集的设计思路和基于Region的内存布局形式。

基于 Region 的堆内存布局

把连续的Java堆划分为多个大小相等的独立区域(Region),每一个 Region 都可用根据需分配给新生代Eden空间、Suvivor空间或者老年代空间。收集器对不同空间的 Region 块采用不同的策略处理。

Region 中还有一类特殊的 Humongous 区域(被视为老年代的一部分),专门用于存储大对象。G1认为只要大小超过一个Region块容量一半的对象就是大对象。每个 Region 大小为 1M ~ 32M,对于超过了整个 Region 容量的超级大对象会被存储到N个连续的Humongous Region块中。

G1虽然仍然保留了新生代和老年代的概念,但新生代和老年代的空间和位置不再是固定的,是一系列Region的集合。

G1收集器工作过程

  • 初始标记(STW): 标记 GC Roots 能直接关联到的对象
  • 并发标记: 从 GC Roots 直接关联对象开始遍历整个对象图,不需要停顿用户线程
  • 最终标记(STW): 处理并发标记阶段结束后仍然遗留下来的STAB(原始快照)记录
  • 筛选回收: 对各个Region块的回收价值和成本进行排序,根据期望的停顿时间指定回收计划,可用自由选择任意多个Region回收,然后将被回收的Region块中存活的对象复制到空的Region中

Java GC基础知识的更多相关文章

  1. JAVA相关基础知识

    JAVA相关基础知识 1.面向对象的特征有哪些方面 1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面.抽象并不打算了解全部问题,而只是选择其中的一部分, ...

  2. Java 多线程——基础知识

    java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...

  3. java必备基础知识(一)

    学习的一点建议: 每一门语言的学习都要从基础知识开始,学习是一个过程,"万丈高楼平地起",没有一个好的地基,想必再豪华的高楼大厦终究有一天会倒塌.因此,我们学习知识也要打牢根基,厚 ...

  4. 什么才是java的基础知识?

    近日里,很多人邀请我回答各种j2ee开发的初级问题,我无一都强调java初学者要先扎实自己的基础知识,那什么才是java的基础知识?又怎么样才算掌握了java的基础知识呢?这个问题还真值得仔细思考. ...

  5. java部分基础知识整理----百度脑图版

    近期发现,通过百度脑图可以很好的归纳总结和整理知识点,本着学习和复习的目的,梳理了一下java部分的知识点,不定期更新,若有不恰之处,请指正,谢谢! 脑图链接如下:java部分基础知识整理----百度 ...

  6. JAVA学习基础知识总结(原创)

    (未经博主允许,禁止转载!) 一.基础知识:1.JVM.JRE和JDK的区别: JVM(Java Virtual Machine):java虚拟机,用于保证java的跨平台的特性. java语言是跨平 ...

  7. Java多线程基础知识笔记(持续更新)

    多线程基础知识笔记 一.线程 1.基本概念 程序(program):是为完成特定任务.用某种语言编写的一组指令的集合.即指一段静态的代码,静态对象. 进程(process):是程序的一次执行过程,或是 ...

  8. JAVA多线程基础知识(一)

    一. 基础知识 要了解多线程首先要知道一些必要的概念,如进程,线程等等.开发多线程的程序有利于充分的利用系统资源(CPU资源),使你的程序执行的更快,响应更及时. 1. 进程,一般是指程序或者任务的执 ...

  9. Java SE 基础知识(一)

    一.基础知识 1. Java SE : Java Standard Edition Java ME : Java Micro Edition Java EE : Java Enterprise Edi ...

  10. java正则表达式基础知识(转)

    1基础 2.1 简单字符类 构造 描述 [abc] a,b或c [^abc] 除a,b或c外的字符 [a-zA-Z] a至z 或 A至Z [a-d[m-p]] a至d 或 m至p [a-z&& ...

随机推荐

  1. 2003031118-李伟-Python数据分析第三周作业-第一次作业

    项目 NumPy数值计算基础 博客名称 2003031118-李伟-Python数据分析第三周作业-第一次作业 课程班级博客链接 https://edu.cnblogs.com/campus/pexy ...

  2. 三、JMeter实战-快速上手JMeter

    1.JMeter基本操作 线JMeter最基本的操作有三个步骤: 先添加一个线程组. 添加HTTP请求. 添加查看结果树. 1.1.添加线程组 在测试计划下新建一个线程组 1.2.添加HTTP请求 在 ...

  3. 掷骰子【普通线性DP】【转移方程可以优化为矩阵快速幂】

    掷骰子 思路 可以先定义一个状态f[i] [j]: 前i个骰子,最后一个面是j的方法数, 肯定超时,然鹅可以混一些分,代码如下 for(int i=1;i<=6;i++) f[0][i]=1; ...

  4. shell命令查找文件

    1.find命令的参数下面是find命令一些常用参数的例子,有用到的时候查查就行了,像上面前几个贴子,都用到了其中的的一些参数,也可以用man或查看论坛里其它贴子有find命令手册使用name选项文件 ...

  5. 深入理解Linux系统调用

    1.系统调用号查询 我的学号位数是08,在64位调用表里可以查到对应的系统调用函数是__x64_sys_lseek 2.lseek函数 由于没用过该函数,所以先去了解一下这个函数的作用: 直白的说就是 ...

  6. ABP vNext微服务架构详细教程(补充篇)——单层模板

    1. 简介 在之前的<ABP vNext微服务架构详细教程>系列中,我们已经构建了完整的微服务架构实例,但是在开发过程中,我们会发现每个基础服务都包含10个类库,这是给予DDD四层架构下A ...

  7. 使用loadrunner运行中问题(无代码生成解决方法)

    开始录制之后,不能成功录制,工具栏events一直是2,打开新网站不跳动,结束录制之后没有代码生成 进入软件,点击工具栏的录制,选择录制选项,将http高级如下设置 同时也要修改套接字,如下配置 当开 ...

  8. Mitmproxy 拦截、mock移动设备网络请求

    转载于https://blog.csdn.net/countofdane/article/details/82055173 1.  安装 pip install mitmproxy 2.  启动 mi ...

  9. Axios的相关应用

    Axios 的案例应用 要求利用axios实现之前利用AJAX实现的验证用户是否登录的案例 鉴于这两种语法的相似性,只需要在AJAX里面的注册界面里面的script标签里面包含的代码修改为如下代码即可 ...

  10. P4774 倚天屠龙传 题解

    其实这道题的主体并不难,主要是细节很多 我们可以把题目分成界限分明的两部分,第一部分,屠每条龙所用的剑只和当前拥有的剑有关.于是可以单独开一个数据结构按题目维护. 另一部分找到最小攻击次数,可以化作以 ...