Java GC的标记-清除算法【总结】_标记清除算法怎么标记的-程序员宅基地

技术标签: GC的标记清除算法  Java相关总结  

Java GC(Garbage Collector)标记-清除算法:

1、标记清除算法: 点击了解:Java的内存管理
GC标记-清除算法由标记阶段清除阶段构成,在标记阶段会把所有的活动对象都做上标记,然后在清除阶段会把没有标记的对象,也就是非活动对象回收。
名词解释:
对象:在GC的世界里对象指的是通过应用程序利用的数据集合,是GC的基本单位。一般由头(header)和域(field)构成。
活动对象:能通过引用程序引用的对象就称为活动对象。(可以直接或间接从全局变量空间中引出的对象)
非活动对象:不能通过程序引用的对象被称为非活动对象。(这就是被清除的目标)
标记清除的伪代码如下:

func mark_sweep{
    
	mark_phase(); 	//标记阶段
	sweep_phase(); 	//清除阶段
}

2、:标记阶段:
标记阶段就是遍历对象并标记的处理过程。
标记阶段的伪代码如下:

func mark_phase(){
    
	for( r : $roots)	//在标记阶段,会给所有的活动对象打上标记
		mark(*r)
}
func mark(){
    
	if(obj.mark == False)
		obj.mark = True		//先标记找出活动对象
		for(child : children(obj))	//然后递归的标记通过指针数据能访问到的对象
			mark(*child)
}

这里$root是指针对象的起点,通过$root可以遍历全部活动对象。

下面是标记前标记后内存中堆的状态:
在这里插入图片描述
在这里插入图片描述
3、:清除阶段:
在清除阶段,collector会遍历整个堆,回收没有打上标记的对象(垃圾),使(堆)其能再次利用。
sweep_phase()函数伪代码实现如下:

func sweep_phase(){
    
	sweeping = $heap_start	//首先将首地址赋值给sweeping
	while(sweeping < $head_end){
    
		if(sweeping.mark == True)
			//如果是标记状态就设为False,如果是活动对象,还会在标记阶段被标记为TRUE
			sweeping.mark == FALSE
		else:
			sweeping.next = $free_list	//将非活动对象 拼接到$free_list 头部位置
			$free_list = sweeping
			sweeping += sweeping.size
	}
}

注:
size域指的是存储对象大小的域,在对象头中事先定义。
next域只在生成空闲链表中取出分块时才会用到。
分块(chunk)这里是指为 利用对象而事先准备出来的空间。
内存中区块的块生路线为 分块--> 活动对象 --> 垃圾 --> 分块 --> ...

在清除阶段我们会把非活动回收再利用。回收对象就是把对象作为分块,连接到被称为空闲链表的单向链表。之后再分配空间时只需遍历这个空闲链表就可以找到分块了。

下图是清除阶段结束后堆的状态:
在这里插入图片描述
4、分配:
回收垃圾的目的是为了能再次分配。

当程序申请分块时,怎样才能把大小合适的分块分配给程序呢?
分配伪代码如下:

func new_obj(size){
    
	chunk = pickup_chunk(size,$free_list)	//遍历$free_list寻找大于等于size的分块
	if(chunk != null)
		return chunk
	else
		allocation_fail() //如果没找到大小合适的分块 提示分配失败
}

pickup_chunk()函数不止返回和size大小相同的分块,也会返回大于size大小的分块(这时会将其分割成size大小的分块和去掉size后剩余大小的分块,并把剩余部分还给空闲链表)。
分配有三种策略:
First-fit:发现大于等于size的分块立刻返回
Best-fit:找到大小和size相等 的分块再返回
Worst-fit:找到最大的分块,然后分割成size大小和剩余大小(这种方法容易产生大量小的块)

5、合并:
根据分配策略的不同,分配过程中会出现大量小的分块,如果分块是连续的,我们就可以把小分块合并生成一个大的分块,合并是在清除阶段完成的,包含了合并策略的清除代码如下:

func sweep_phase(){
    
    sweeping = $heap_start            // 首先将堆的首地址赋值给 sweeping
    while(sweeping < $head_end){
    
        if(sweeping.mark == TRUE)
            // 如果是标记状态就设为 FALSE,如果是活动对象,还会在标记阶段被标记为 TRUE
            sweeping.mark == FALSE    
        else:
            if(sweeping == $free_list + $free_list.size)  // 堆的地址正好和空闲链表大小相同
                $free_list.size += sweeping.size
            else
                sweeping.next = $free_list   // 将非活动对象 拼接到 $free_list 头部位置
                $free_list = sweeping
        sweeping += sweeping.size
    }     
}

$heap_end = $heap_start + HEAP_SIZE
所以这里sweeping == $free_list + $free_list.size可以理解为需要清除的堆的地址正好和空闲链接相邻

6、优缺点:
优点:
a、实现简单。
b、与保守式GC算法兼容。
缺点:
a、碎片化严重(由上面描述的分配算法克制,容易产生大量小的分块)
b、分配速度慢(由于空闲区块是用链表实现,分块可能是不连续的,每次分配都需要遍历空闲链表, 极端情况是需要遍历整个链表的)。
c、与写时复制技术不兼容

写时复制(copy-on-write)是众多UNIX操作系统用到的内存优化方法,比如在linux系统中使用fork()函数复制进程时,大部分内存空间都不会被复制,只是复制进程,只有在内存中内容被改变时才会被复制内存数据,但是如果使用标记清除算法,这时内存会被设置标志位,就会频繁发生不应该发生的复制。

多个空闲链表:
上面所说的标记清除算法只用到了一个空闲链表对大小不一的分块统一处理,但这样做每次都需要遍历一遍来寻找大小合适的分块,非常浪费时间。
这里我们使用多个空闲链表的方法来储存活动对象。比如:将两个字的分块组成一个空闲链表,三个字的分块组成另一块空闲链表,等等。。。
这时,如果我们需要分配三个字的分块,那我们值需要查询对应的三个字的空闲链表就可以了。

到底需要制造多小个空闲链表呢?
因为通常程序不会申请特别大的分块,所以我们通常给分块设置一个上限,比如100大于这个上限的组成一个特殊的空闲链表。这样101个空闲链表就够了。

位图标记:
在单纯的GC标记-清除算法中,用于标记的位置是被分配到对象头中去的,算法是把对象和头一并处理,但这个写时复制不兼容。
位图标记法是只收集各个对象的标志位并表格化,不和对象一起管理,在标记的时候不在对象的头里设置位置,而是在特定的表格中置位。
在这里插入图片描述

位图标记中重要的是,位图表格中位的位置要和堆里的各个对象切实对应,一般来说堆中的一个字会分配到一个位。

位图标记中mark()函数的伪代码实现如下:

func mark(obj){
    
    obj_num = (obj - $heap_start) / WORD_LENGTH  // WORD_LENGTH 是一个常量,表示机器中一个字的位宽
    index = obj_num / WORD_LENGTH
    offset = obj_num % WORD_LENGTH
    
    if ($bitmap_tbl[index] & (1 << offset)) == 0
        $bitmap_tbl[index] |= (1 << offset)
        for (child: children(obj)) // 然后递归的标记通过指针数组能访问到的对象
            mark(*child)
}

这里 obj_num 指的是从位图表格前面数,obj 的标志位在第几个。例如 E 的 obj_num 是8。
obj_num 除以 WORD_LENGTH 得到的商 index 以及余数 offset 来分别表示位图表格的行编号和列编号。
优点:
a、和写时复制技术兼容
b、清除更高效(只需要遍历位图表格就可以,清除的时候也只需要清除表格中的标志位)。

延迟清除:
清除操作所花费的时间和堆的大小成正比,堆越大,标记-清除 动作花费的时间越长,也就越影响程序的运行。

延迟清除(lazy sweep)是缩短清除操作花费导致程序最大暂停时间的方法。

最大暂停时间,因执行 GC 而暂停执行程序的最长时间。

延迟清除中 new_obj() 函数会在分配的时候调用 lazy_sweep()函数,进行清除操作。如果它能用清除操作来分配分块,就会返回分块,如果不能分配分块,就会执行标记操作。然后重复这个步骤,直到找到分块或者allocation_fail

通过延迟清除法可以缩减程序的暂停时间,不过延迟效果并不是均衡的。
比如下图这种刚标记完堆的情况:
在这里插入图片描述
这时,活动对象和非活动对象都是相邻分布,如果程序在活动对象周围开始清除,那它找到的对象都是活动对象不可清除,只能不停遍历,暂停时间就会变长。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_44296929/article/details/102663989

智能推荐

PHP7安装pdo_mysql扩展-程序员宅基地

文章浏览阅读2.9k次。因为自己在编译安装php7.2.7的时候,没有留意pdo_mysql失败。但是重新编译安装php7.2.7需要和长时间。百度了下,linux 有个 autoconfyum install autoconf -y 打开php安装包路径找到pdo_mysql进入文件夹检查扩展包是否问题/datas/soft/php72/bin/phpize设置..._php7 安装 pdo_mysql error: unknown type name ‘my_bool’ my_bool *in_null;

python自动化运维快速入门,python自动化运维项目-程序员宅基地

文章浏览阅读538次,点赞16次,收藏8次。大家好,本文将围绕python自动化运维需要掌握的技能展开说明,python自动化运维从入门到精通是一个很多人都想弄明白的事情,想搞清楚python自动化运维快速入门 pdf需要先了解以下几个事情。这篇文章主要介绍了一个有趣的事情,具有一定借鉴价值,需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获,下面让小编带着大家一起了解一下。

Web_python_template_injection(Python模块注入)-程序员宅基地

文章浏览阅读1.1k次,点赞5次,收藏8次。Python的模块注入 flask/jinja2 常用于ssti的魔术方法 获取基类的几种方法 获取基本类的子类 采用os模块的listdir函数来读取目录 常用payload_web_python_template_injection

R语言与网站分析 第8章样本分…_r语言uniqueness-程序员宅基地

文章浏览阅读1.6k次。第八章:样本细分8.1数据降维因子载荷(loading):定义:第8章样本分析:聚类分析" TITLE="R语言与网站分析 第8章样本分析:聚类分析" />第8章样本分析:聚类分析" TITLE="R语言与网站分析 第8章样本分析:聚类分析" />5.特征值和信息损失率 P2966.因子得分:计算好因子载荷A和特殊因子e后,计算因子F的数据。计算方式有:加权最小二乘法(Bartle_r语言uniqueness

Sketch webView方式插件开发技术总结-程序员宅基地

文章浏览阅读935次,点赞25次,收藏14次。Sketch作为一款广受欢迎的矢量图形设计工具,其功能远不止基础的矢量设计,它的真正实力部分源自其丰富的插件生态系统。Sketch采用JavaScript API作为插件开发的主要手段,这一机制允许开发者利用JavaScript编写插件代码,借助CocoaScript桥梁与Sketch的内部API以及macOS框架无缝对接。因此,即便是熟悉Web前端技术的开发者也能相对轻松地涉足Sketch插件开发领域,创造出更多提升设计效率和功能性的插件解决方案。

mysql 查询全部视图_mysql中怎么查看所有的视图名?-程序员宅基地

文章浏览阅读3k次。本帖最后由 jan_1985 于 2014-2-12 16:49 编辑当前用户有权限查看的整个数据库的视图的名称与定义语句都会列出来。mysql> select * from information_schema.VIEWS;+---------------+--------------+------------+---------------------------------------..._mysql全局搜索视图

随便推点

【网格生成】Gmsh快速入门教程 --3.Gmsh API_gmsh的api配置-程序员宅基地

文章浏览阅读4.4k次。在前面两篇文章1、2中我们分别介绍了图形化界面和内置解析器geo脚本的使用方式。今天来介绍下Gmsh的第三种使用方式:使用Gmsh API将其集成到其他软件中。意义将网格生成器与求解器等软件对接形成整体框架。获取Gmsh API几种方式通过官网下载SDK http://www.gmsh.info/bin/Windows/gmsh-git-Windows64-sdk.zippip install --upgrade gmsh (Python)在编译时加上 cmake -DENABLE_BUILD_gmsh的api配置

Ubuntu的复制粘贴操作及常用快捷键_ubuntu copy kuai jie jian-程序员宅基地

文章浏览阅读9w次,点赞22次,收藏107次。Ubuntu的复制粘贴操作 1.最为简单,最为常用的应该是鼠标右键操作了,可以选中文件,字符等,右键鼠标,复制,到目的地右键鼠标,粘贴就结束了。2.快捷键。一般通用的是Ctrl+C与Ctrl+V。不过通用也是有限制的,一般的程序下是没有问题,遇到终端就不行了。其实终端下默认的是 Ctrl+Shift+C,Ctrl+Shift+V,可以自己在编辑项下面自己设置为常用的。3.文件_ubuntu copy kuai jie jian

基于Springboot的宠物医院管理系统-JAVA【毕业设计、论文、源码、开题报告】_基于springboot的宠物医院管理系统国内外研究现状-程序员宅基地

文章浏览阅读5.8k次,点赞8次,收藏92次。1 绪论1.1 课题背景在信息技术高速发展的今天,新知识、新技术层出不穷,计算机技术早已广泛的应用于各行各业之中,利用计算机的强大数据处理能力和辅助决策能力叫,实现行业管理的规范化、标准化、效率化。管理信息系统(Management Information System,简称MIS〉是一个以人为主导,利用计算机软硬件技术以及网络通信技术,实现对信息的收集、传输、储存、更新。目前,管理信息系统广泛采用WEB技术作为开发的主要技术。在经过多年的技术积累与更新,WEB技术已经从一种简单的信息浏览和信息交互平台发展_基于springboot的宠物医院管理系统国内外研究现状

C++多线程:condition_variable_c++ condition_variable-程序员宅基地

文章浏览阅读2.6k次,点赞5次,收藏26次。官方定义在多线程编程中,有一种十分常见的行为:线程同步。线程同步是指线程间需要按照预定的先后次序顺序进行的行为。C++11对这种行为也提供了有力的支持,这就是条件变量(condition_variable和condition_variable_any)。条件变量位于头文件condition_variable下。condition_variable/condition_variable_any类是一个synchronization primitive,可用于阻止一个线程或同时阻止多个线程,直到另一个线程修改共_c++ condition_variable

用Latex写伪代码_latex编辑中文伪代码显示行号-程序员宅基地

文章浏览阅读4k次,点赞2次,收藏5次。algorithmic和algorithmicx介绍下algorithmic和algorithmicx,这两个包很像,很多命令都是一样的,只是algorithmic的命令都是大写,algorithmicx的命令都是首字母大写,其他小写(EndFor两个大写)。下面是algorithmic的基本命令:\STATE \IF{} \STATE{} \ENDI_latex编辑中文伪代码显示行号

简单的有道词典_有道词典的类csdn-程序员宅基地

文章浏览阅读349次。import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.io.FileNotFoundException;import java.io.FileReader;import java.io.IOException;import java.util.Properties;_有道词典的类csdn

推荐文章

热门文章

相关标签