深入解析java虚拟机:垃圾回收,垃圾回收基础概述
yuyutoo 2024-10-11 23:55 3 浏览 0 评论
垃圾回收基础概述
垃圾回收机制最早诞生于Lisp编程语言,但Lisp的作者McCathy在第一次现场演示Lisp时却因中途耗尽全部32KB内存以及一些其他原因只能草草收场。60年后的今天,垃圾回收技术再也不是一个笑话,它俨然成为诸如Java、C#、Python、Erlang、Golang编程语言的核心组件。
Java最吸引人的特性之一就是它的垃圾回收技术:程序员负责创建对象、使用对象,垃圾回收器负责回收资源,做好善后工作。它从GCRoot出发标记存活对象,清理未被标记的对象,这种方式又被称为追踪式回收。Java的所有垃圾回收器都使用追踪式回收,只是具体的算法细节不尽相同。本章将讨论HotSpot VM中现存的所有垃圾回收器,在这之前,有必要先了解下垃圾回收的一些基础知识。
GC Root
GC Root又叫根集,它是垃圾回收器扫描存活对象的起始地点。举个简单的例子,如代码清单10-1所示:
代码清单10-1 GC Root示例
public static void test(){
Struct free = new Struct(“a”);
Struct obj = new Struct(“b”);
obj.field1 = new Object();
obj.field2 = 12;System.gc(); // (1)
free = null;
System.gc(); // (2)
}
假设(1)处成功触发垃圾回收,那么垃圾回收器将不能回收任何对象,因为线程栈上包括free和obj引用,它们分别指向对象a和对象b。
在(2)处调用成功后,垃圾回收器可以回收对象a但不能回收对象b,因为栈上存在指向对象b的引用obj,而指向对象a的引用free被赋予null值,即再没有指向对象a的引用,因此对象a被视作垃圾,可回收处理。
代码清单10-1中的free和obj所在的线程栈即GC Root之一,垃圾回收器以它们为起点找到存活对象:凡是从线程栈出发没有触及的对象就可以被认为是死亡对象,继而可以被回收。除了线程栈外,HotSpot VM还有一些地方也可以作为GC Root:
1)所有已加载的类的对象引用(ClassLoaderDataGraph::roots_cld_do);
2)所有线程栈上的对象引用(Threads::possibly_parallel_oops_do);
3)虚拟机内部使用的Java对象引用(Universe::oops_do,SystemDictionary::oops_do);
4)JNI Handle(JNIHandles::oops_do);
5)被synchronized锁住的对象引用(ObjectSynchronizer::oops_do);
6)Java工具用到的对象引用(Management::oops);
7)JVMTI导出对象引用(JvmtiExport::oops_do);
8)AOT堆对象引用(AOTLoader::oops);
9)CodeCache代码引用(CodeCache::blobs_do);
10)String常量池对象引用(StringTable::oops_do)。
安全点
在垃圾回收器的眼中只有垃圾回收线程和修改对象的线程,后者被称为Mutator线程。由于垃圾回收线程也需要修改对象,尤其是在垃圾回收过程中可能有移动对象的情况,如果Mutator线程在移动对象的同时修改对象,势必会造成错误,因此在垃圾回收时一般需要全过程,或者部分过程暂停Mutator线程,这种暂停Mutator线程的现象又叫作世界停顿(Stop The World,STW)。一般来说,Mutator线程可以主动或者被动达到STW,在HotSpot VM中,使用安全点(Safepoint)作为主动STW机制。安全点本质上是一页内存,如代码清单10-2所示:
代码清单10-2 安全点创建
void SafepointMechanism::default_initialize() {
if (ThreadLocalHandshakes) {
...// 分配两页内存,一页用于bad_page,一页用于good_page
char* polling_page = os::reserve_memory(...);
char* bad_page = polling_page;
char* good_page = polling_page + page_size;
// bad_page表示这片内存不可读不可写,good_page表示可读
os::protect_memory(bad_page, page_size, os::MEM_PROT_NONE);
os::protect_memory(good_page, page_size, os::MEM_PROT_READ);
os::set_polling_page((address)(bad_page));
...
} else {
// 分配一页内存
char* polling_page = os::reserve_memory(...);
os::commit_memory_or_exit(...);
// 将它设置为可读
os::protect_memory(polling_page, page_size, os::MEM_PROT_READ);
os::set_polling_page((address)(polling_page));
}
}
虚拟机将“读取安全点内存页”的操作安插在一些合适的地方。当程序没有请求垃圾回收时(实际上除了垃圾回收外,还有其他操作可能会请求安全点),安全点内存页可读,Mutator线程对安全点的访问不会引发任何问题。当需要垃圾回收时,VMThread将安全点设置为不可读不可写,然后等待所有Mutator线程走到安全点。由于Mutator线程访问不可读不可写的内存时会引发异常信号,虚拟机可通过内部的信号处理器捕获并停止Mutator线程的执行,这样一来相当于让所有Mutator线程主动停止。
在具体实现中,SafepointSynchronize::begin()和SafepointSynchronize::end()分别表示安全点的开启和关闭,两者之间构成一个安全区域,它们只能被VMThread调用。代码清单10-3展示了安全点开启的代码实现:
代码清单10-3 SafepointSynchronize::begin
void SafepointSynchronize::begin() {
...
// 设置状态为安全点开启中
_state = _synchronizing;
// 如果使用全局安全点,修改安全点内存页,将其设置为不可读不可写
// (对应还有如果使用线程握手的处理,这里已省略)
if (SafepointMechanism::uses_global_page_poll()) {
Interpreter::notice_safepoints();
PageArmed = 1 ;
os::make_polling_page_unreadable();
}
...
while(still_running > 0) {
jtiwh.rewind();
// 对于当前所有运行的线程
for (; JavaThread *cur = jtiwh.next(); ) {
// 获取线程状态
ThreadSafepointState *cur_state = cur->safepoint_state();// 如果还在运行
if (cur_state->is_running()) {
// 检查线程是不是suspend或者其他情况,并处理它
cur_state->examine_state_of_thread();
// 再次检查线程是否还在运行
if (!cur_state->is_running()) {
// 如果没有运行,计数减一(still_running表示当前还在运行的线程)
still_running--;
}
}
}
... // 如果循环太多次,可能会使当前线程暂停
}
// VMThread等待所有线程停下来
while (_waiting_to_block > 0) {
... Safepoint_lock->wait(true, remaining_time / MICROUNITS);
}
// 安全点开启成功,设置状态,计数增加
_safepoint_counter ++;
_state = _synchronized;
OrderAccess::fence();
... // 日志记录等
}
VMThread会等待所有线程,直到都达到安全点,此时安全点开启成功。开启安全点的核心是线程状态的转换,不同线程进入安全点的方式也不尽相同。
1)解释器线程:第5章提到过,VMThread调用TemplateInterpreter::notice_safepoints通知模板解释器将模板表切换为安全点表(这意味着执行完一条字节码后遇到一个安全点时,可以进入安全点),安全点表除了执行字节码代码外还负责安全点处理,其中就包括进入安全点。
2)执行native代码的线程:VMThread不会暂停执行native代码的线程,但是当线程从native代码返回到Java代码时,需要检查_state,如果发现是_synchronizing则线程停止。
3)执行编译后的代码的线程:开启安全点后,执行编译后的代码的线程使用test指令访问安全点,此时安全点不可读,所以引发异常信号,异常信号会被虚拟机的信号处理器(在Linux平台上是handle_linux_signal)捕获,然后阻塞线程。
4)已经阻塞的线程:对于已经阻塞的线程,继续保持阻塞状态即可,在安全点操作没有结束前不允许醒来。
5)执行虚拟机内部代码或者正在状态转换的线程:Java线程大部分时间在执行字节码,有时也会执行虚拟机自身的一些代码,这些线程会在状态转换时阻塞自身。
线程局部握手
上节节的代码清单10-2展示的代码中有一个线程局部握手(ThreadLocal Handshakes)标志,它是JEP 312引入的特性。根据上面的描述,安全点是一个全局的内存页,一旦VMThread开启安全点(将内存设置为不可读不可写)后,所有Mutator线程都会继续运行直到遇到附近的安全点读取,再通过异常处理机制主动停止。但是有时并不需要停止所有Mutator线程,如偏向锁撤销,或者打印某个线程的线程栈,在这些情况下,VMThread只需要停止某个指定的线程并打印线程栈即可。基于这些考虑,HotSpot VM引入了线程局部握手机制,使VMThread可以有选择性地针对某个线程开启或者关闭线程局部的安全点。
GC屏障
GC屏障即后缀为BarrierSet的一系列类,它们的作用是在字段读操作或者写操作前后插入一段代码,执行某些垃圾回收必要的逻辑,如代码清单10-4所示:
代码清单10-4 GC屏障
public void barrier(Struct obj){
// Write_barreir_pre();
obj.field = new Object();
// Write_barreir_post();
}
虚拟机在字段写操作前后可以分别插入前置写屏障、后置写屏障(读屏障同理),这些写屏障会执行一些GC必要的逻辑,如检测到对象引用关系的修改并记录到记忆集中。GC屏障对性能有较大影响,因为字段读写操作是程序最常见的行为,所以不应该在GC屏障中放置“重量级”代码。
本文给大家讲解的内容是深入解析java虚拟机:垃圾回收,垃圾回收基础概述
- 下篇文章给大家讲解的是深入解析java虚拟机:垃圾回收,Epsilon GC;
- 觉得文章不错的朋友可以转发此文关注小编;
- 感谢大家的支持!
相关推荐
- 史上最全的浏览器兼容性问题和解决方案
-
微信ID:WEB_wysj(点击关注)◎◎◎◎◎◎◎◎◎一┳═┻︻▄(页底留言开放,欢迎来吐槽)●●●...
-
- 平面设计基础知识_平面设计基础知识实验收获与总结
-
CSS构造颜色,背景与图像1.使用span更好的控制文本中局部区域的文本:文本;2.使用display属性提供区块转变:display:inline(是内联的...
-
2025-02-21 16:01 yuyutoo
- 写作排版简单三步就行-工具篇_作文排版模板
-
和我们工作中日常word排版内部交流不同,这篇教程介绍的写作排版主要是用于“微信公众号、头条号”网络展示。写作展现的是我的思考,排版是让写作在网格上更好地展现。在写作上花费时间是有累积复利优势的,在排...
- 写一个2048的游戏_2048小游戏功能实现
-
1.创建HTML文件1.打开一个文本编辑器,例如Notepad++、SublimeText、VisualStudioCode等。2.将以下HTML代码复制并粘贴到文本编辑器中:html...
- 今天你穿“短袖”了吗?青岛最高23℃!接下来几天气温更刺激……
-
最近的天气暖和得让很多小伙伴们喊“热”!!! 昨天的气温到底升得有多高呢?你家有没有榜上有名?...
- CSS不规则卡片,纯CSS制作优惠券样式,CSS实现锯齿样式
-
之前也有写过CSS优惠券样式《CSS3径向渐变实现优惠券波浪造型》,这次再来温习一遍,并且将更为详细的讲解,从布局到具体样式说明,最后定义CSS变量,自定义主题颜色。布局...
- 你的自我界限够强大吗?_你的自我界限够强大吗英文
-
我的结果:A、该设立新的界限...
- 行内元素与块级元素,以及区别_行内元素和块级元素有什么区别?
-
行内元素与块级元素首先,CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,分别为块级(block)、行内(inline)。块级元素:(以下列举比较常...
-
- 让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华
-
去年的两会期间,习近平总书记在参加人大会议四川代表团审议时,对治蜀兴川提出了明确要求,指明了前行方向,并带来了“祝四川人民的生活越来越安逸”的美好祝福。又是一年...
-
2025-02-21 16:00 yuyutoo
- 今年国家综合性消防救援队伍计划招录消防员15000名
-
记者24日从应急管理部获悉,国家综合性消防救援队伍2023年消防员招录工作已正式启动。今年共计划招录消防员15000名,其中高校应届毕业生5000名、退役士兵5000名、社会青年5000名。本次招录的...
- 一起盘点最新 Chrome v133 的5大主流特性 ?
-
1.CSS的高级attr()方法CSSattr()函数是CSSLevel5中用于检索DOM元素的属性值并将其用于CSS属性值,类似于var()函数替换自定义属性值的方式。...
- 竞走团体世锦赛5月太仓举行 世界冠军杨家玉担任形象大使
-
style="text-align:center;"data-mce-style="text-align:...
- 学物理能做什么?_学物理能做什么 卢昌海
-
作者:曹则贤中国科学院物理研究所原标题:《物理学:ASourceofPowerforMan》在2006年中央电视台《对话》栏目的某期节目中,主持人问过我一个的问题:“学物理的人,如果日后不...
-
- 你不知道的关于这只眯眼兔的6个小秘密
-
在你们忙着给熊本君做表情包的时候,要知道,最先在网络上引起轰动的可是这只脸上只有两条缝的兔子——兔斯基。今年,它更是迎来了自己的10岁生日。①关于德艺双馨“老艺...
-
2025-02-21 16:00 yuyutoo
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)