C++ RAII在HotSpot VM中的重要应用

c++,raii,hotspot,vm,重要,应用 · 浏览次数 : 135

小编点评

**RAII(Resource Acquisition Is Initialization)** **定义:** RAII是一种C++语言中的管理资源、避免泄漏的惯用法。它利用栈对象在离开作用域后自动析构的语言特点,将受限资源的生命周期绑定到该对象上,当对象析构时以达到自动释放资源的目的。 **主要功能:** * 使用栈对象在离开作用域后自动析构对象。 * 确保对象在对象生命期控制范围之下对资源的访问始终保持有效。 * 减少资源泄漏的风险。 **使用场景:** * 管理内存资源,例如内存指针、文件句柄等。 * 确保资源与对象的生命周期绑定。 * 实现安全、简洁的状态管理。 **关键概念:** * **资源:**对象需要访问的资源,例如内存、文件等。 * **资源管理器:**负责管理资源的获取、分配和释放。 * **栈:**在函数调用过程中,栈对象用来存储函数调用过程中的所有局部变量。 * **资源对象:**与资源关联的对象,负责在资源不再需要时释放它。 **优点:** * 减少资源泄漏。 * 提升代码可读性。 * 简化资源管理。 **注意:** * RAII不是必须的,但是一种常用的技术。 * 在使用RAII时,需要考虑性能和内存使用等因素。

正文

RAII(Resource Acquisition Is Initialization),也称为“资源获取就是初始化”,是C++语言的一种管理资源、避免泄漏的惯用法。C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII的做法是使用一个对象,在其构造时获取资源,在对象生命期控制范围之下对资源的访问始终保持有效,最后在对象析构的时候释放资源。
在HotSpot VM中,RAII对内存资源的管理和释放、明确定义范围锁及记录重要信息等方面起到了非常重要的作用。下面详细介绍一下。

1、定义范围锁

在HotSpot VM中,整个系统正确的运转需要非常多的锁,这些锁很多都是通过RAII技术来管理的。
举个例子,如下:

class MutexLocker {
private:
    pthread_mutex_t *_mtx;
public:
    MutexLocker(pthread_mutex_t *mtx) {
        if (mtx) {
            _mtx = mtx;
            pthread_mutex_lock(_mtx);
        }
    }

    ~MutexLocker() {
        if (_mtx)
            pthread_mutex_unlock(_mtx);
    }
};

在类的构造和析构函数中对互斥量进行加载和释放锁。也就是说,当对象创建的时候会自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。

现在我们通过如上的类将一段代码保护起来,防止产生并发问题:

// 初始化互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void init(){
    MutexLocker locker(&mutex);
    // 整个方法都会在同步锁的保护下执行
}

我们还可以通过匿名块来进一步细化锁控制的范围。当进入作用域范围时,C++会自动调用MutexLocker的构造函数,当出了作用域范围时,会调用MutexLocker析构函数。这样通过类来管理锁资源,将资源和对象的生命周期绑定。在Java中有个类似的、饱受诟病的一种释放资源的办法,重写finalize()方法,由于开发人员无法对Java对象的生命周期进行精确控制,而是托管给了Java虚拟机GC,所以对象什么时候回收是一个未知数,为应用程序埋下了一个定时炸弹。不过另外一个类似的语法try-with-resources提倡使用。
在HotSpot VM中,在runtime/mutex.hpp文件中定义了互斥量Mutex,这个互斥量继承自Monitor,HotSpot VM内部的并发非常依赖Monitor。在runtime/mutexLocker.hpp文件中定义了MutexLocker、MutexLockerEx等类来控制锁范围。

2、管理内存资源

管理内存资源的一些类有HandleMark、ResourceMark等,HandleMark用来管理句柄,ResourceMark用来管理临时使用的内存。
HandleMark我在之前已经介绍的非常详细了,可参考如下文章:
第2.7篇-操作句柄Handle
第2.8篇-句柄Handle的释放
ResourceMark的实现也非常类似。
由于Java类常量池中的字符串、还有一些公共字符串在HotSpot VM中都用Symbol实例来表示,如果想要看某个Klass实例表示的具体的类名称,我有时候会这样做:

{
 ResourceMark rm;
 Symbol *sym = _klass->name();
 const char *klassName = (sym->as_C_string());
 // ...
}

调用的as_C_string()函数实现如下:

char* Symbol::as_C_string() const {
  int len = utf8_length();
  char* str = (char*) resource_allocate_bytes( (len + 1) * sizeof(char) );
  return as_C_string(str, len + 1);
}

extern char* resource_allocate_bytes(size_t size, AllocFailType alloc_failmode) {
  ResourceArea* ra = Thread::current()->resource_area();
  return ra->allocate_bytes(size, alloc_failmode);
}

可以看到从ResourceArea中申请了内存,那就必须要记录,完成调用之后恢复调用之前的样子,这样才不会让内存处在不一致的状态,从而导致崩溃,所以必须要使用ResourceMark。

3、保存重要信息

阅读HotSpot VM源代码的人一定会对JavaCalls::call_helper()函数中的如下这段代码不陌生:

从HotSpot VM内部调用Java方法时,通常会调用到call_helper()函数,所以这也是HotSpot VM调用Java主类main()方法的关键入口,在这个函数中我们能够看到HandleMark的使用,另外还有一个JavaCallWrapper,这个类主要有2个作用:
(1)管理内存资源,在 第42篇-JNI引用的管理(1) 已经详细介绍过,这里不再介绍。
(2)记录Java调用栈的重要信息,退栈等操作非常依赖这些信息。
变量名叫link非常贴切,它的起用就是将Java栈连接起来,其大概的实现过程如下图所示。

后面我们在介绍具体的知识点时再详细介绍这些内容。

RAII技术被认为是C++中管理资源的最佳方法,进一步引申,使用RAII技术也可以实现安全、简洁的状态管理,编写出优雅的异常安全的代码。它利用栈对象在离开作用域后自动析构的语言特点,将受限资源的生命周期绑定到该对象上,当对象析构时以达到自动释放资源的目的。

简单而言RAII就是指资源在我们拿到时就已经初始化,一旦不在需要该资源就可以自动释放该资源。

本人最近准备出一个手写Hotspot VM的课程,超级硬核,从0开始写HotSpot VM,将HotSpot VM所有核心的实现全部走一遍,如感兴趣,速速入群。

群里可讨论虚拟机和Java性能剖析与故障诊断等话题,欢迎加入。

与C++ RAII在HotSpot VM中的重要应用相似的内容:

C++ RAII在HotSpot VM中的重要应用

在HotSpot VM中,RAII对内存资源的管理和释放、明确定义范围锁及记录重要信息等方面起到了非常重要的作用。

都说 C++ 没有 GC,RAII: 那么我算个啥?

学过 Java、C# 或者其他托管语言(managed languages)的同学,回过头来看 C++ 的时候,第一反应就是 C++ 没有自动垃圾回收器(GC),而不能充分利用的资源被称为垃圾。

C++判断当前程序是否运行在Windows展台(Kiosk)模式下

Windows有一个展台(Kiosk)模式。展台模式可以使Windows作为数字标牌进行使用。具体请参考Windows 展台 配置完展台模式,重启设备后,Windows会以全屏的方式运行展台应用,无法进入桌面。有点类似iPhone中的引导者模式。此时我们自己的应用如果设置了开机自启,也会运行,但是会

C++获取商店应用(msix应用)桌面快捷方式的安装目录

传统应用的快捷方式目标指向可执行文件的路径,但是对于商店应用(也叫msix打包应用),则指向一个奇怪的字符串,使用IShellLink::GetPath获取路径时,则得到的是空字符串,而我们的最终目的是要拿到应用的安装路径,那该怎么办呢? 首先解释一下,那个奇怪的字符串叫AUMID(App User

C++面向对象

1. C++语言基础 1.1 函数 C++新增:多态 函数重载( overload ) 函数重写(覆写,overrride) 编译器会根据实参的类型来⾃动确定调⽤哪个重载函数 C++新增:内联函数 修饰关键字:inline 作用:编译时直接将函数替换为一堆代码,减少函数调用带来的开销。 比#defi

C++面向对象多级菜单向Arduino的移植

前段时间写了一篇文章《C++面向对象语言自制多级菜单》,文中指出了可以将HeleMenu库进行移植,现已完成技术思路,特此记录。 一、特性 基本与上一篇文章指出的一致,只是将菜单显示和响应函数合二为一 二、代码实现 基本与上一篇文章指出的一致,只是考虑到右值和左值的问题,将形参改为了const类型

「C++」复杂模拟【壹】

建议开启目录食用 阅读本文之前建议您先看这里,如果您已经看完了,那么就可以放心大胆的学习本文了。 我认为其实本文的难度还是比较大的,今天我们题是来自山东省省选,所以建议大家谨慎阅读,如果您是专业程序员当我没说。 OK,那么事不宜迟,咱们来看第一题 [SDOI2010] 猪国杀 题目描述 游戏背景 《

「C++」简单模拟

这是一个公式: \[F_n=\dfrac{\left(\frac{1+\sqrt{5}}{2}\right)^n-\left(\frac{1-\sqrt{5}}{2}\right)^n}{\sqrt{5}} \]根据大家的数学经验可以知道这是一个计算斐波那契数列的公式,那么假设我们不知道这是一个斐波

C++面向对象语言自制多级菜单

因为要做一个小应用,需要一个菜单类,在网上找了许久,也没有找到一款心仪的菜单类,索性用C++语言,自制一个命令行级别的菜单类,并制作成库,现记录下来,供以后借鉴。 一、特性 无限制条目 无限制层级 用户自定义条目和动作 脚本式生成菜单类 二、代码实现 (一)菜单类 菜单类主要负责菜单的创建、修改、删

「C++」论高精度

大家好,我是Charzie。在编程领域,高精度计算是一个常见的问题。当标准的整型或浮点型无法满足我们的计算需求时,高精度计算就显得尤为重要。在C++中,虽然标准库没有直接提供高精度数据类型,但我们可以通过一些技巧和工具类来实现高精度计算。 为什么需要高精度? 在编程中,我们经常会遇到一些大数计算的问