深入解析java虚拟机:垃圾回收,垃圾回收基础概述
yuyutoo 2024-10-11 23:55 13 浏览 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;
- 觉得文章不错的朋友可以转发此文关注小编;
- 感谢大家的支持!
相关推荐
- 网络规划建设原来也可以这么简单!
-
废话少说,直接上干货。天气炎热,请各位看官老爷静心阅读。整体思路下图是关于网络建设的所有相关领域,接下来我为大家逐一讲解。网络分层...
- 网络规划设计师笔记-第 1 章 计算机网络原理
-
计算机网络原理1.1计算机网络概论(P1-10)...
- 别输在远见上,网工这样做职业规划,比啥都强
-
01职业中的规划,人生中的buff“职业规划“这个词,其实对很多年轻人,包括曾经年轻的我来说,都不屑一提。...
- 网络规划设计师学习中(个人自学笔记分享1),有一起学习的吗?
-
网络规划设计师,上午考试内容学习:第一章:计算机网络概述(上部分):如果你也在一起学习,那么我们来一起学习吧!坚持1年,争取明年一次性通过!...
- 在微服务中使用 ASP.NET Core 实现事件溯源和 CQRS
-
概述:事件溯源和命令查询责任分离(CQRS)已成为解决微服务设计的复杂性的强大架构模式。基本CQRS表示形式在本文中,我们将探讨ASP.NETCore如何使你能够将事件溯源和CQRS...
- 用 Nginx 部署 ASP.NET Core 应用程序
-
用Nginx部署ASP.NETCore应用程序步骤如下:在Linux中安装.NETCore运行时和Nginx:...
- Asp.net Core启动流程讲解(一)(asp.net core 入门)
-
asp.netcore默认项目包括项目根目录级的Startup.cs、Program.cs、appsettings.json(appsettings.Development.json)launch...
- 十天学会ASP之第五天(十天学会asp教程)
-
学习目的:学会数据库的基本操作1(写入记录)数据库的基本操作无非是:查询记录,写入记录,删除记录,修改记录。今天我们先学习写入记录。先建立一个表单:<formname="form1"met...
- ASP.NET Core 的 WebApplication 类
-
ASP.NETCore提供了3个主机类(Host)。这些类用于配置应用、管理生命周期和启动Web服务。...
- ASP.NET Core中的键控依赖注入(.net依赖注入原理)
-
大家好,我是深山踏红叶,今天我们来聊一聊ASP.NETCore中的FromKeyedServices,它是在.Net8中引入的。这一特性允许通过键(如字符串或枚举)来注册和检索依赖注入(D...
- Asp.net常用方法及request和response-a
-
asp.net教程asp.net常用方法:1、Request.UrlReferrer请求的来源,可以根据这个判断从百度搜的哪个关键词、防下载盗链、防图片盗链,可以伪造(比如迅雷)。(使用全局一般处理...
- asp.net常考面试题(aspnet题库)
-
asp.net常考面试题一,列举ASP.Net页面之间传递值的几种方式?1,使用QueryString,如:......?id=1;response.Redirect()......2,使用Sessi...
- 在Windows系统搭建.NET Core环境并创建运行ASP.NET网站
-
微软于6月27日在红帽DevNation峰会上正式发布了.NETCore1.0、ASP.NET1.0和EntityFrameworkCore1.0,其将全部支持Windows、OSX和...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 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)