js引擎v8源码分析之Handle(基于v8 0.1.5)

2024-03-27 21:32
文章标签 分析 源码 js 引擎 0.1 v8 handle

本文主要是介绍js引擎v8源码分析之Handle(基于v8 0.1.5),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Handle是使用v8的时候很重要的一个概念和类。他本质是堆对象的封装。我们通过Handle管理真正的对象,而不是直接操作对象。Handle在v8中有两个实现。一个是对外使用的一个是内部使用的。我们先看一下内部使用的。

1 内部handle

template<class T>
class Handle {public:INLINE(Handle(T** location))  { location_ = location; }INLINE(explicit Handle(T* obj));INLINE(Handle()) : location_(NULL) {}template <class S> Handle(Handle<S> handle) {location_ = reinterpret_cast<T**>(handle.location());}INLINE(T* operator ->() const)  { return operator*(); }bool is_identical_to(const Handle<T> other) const {return operator*() == *other;}INLINE(T* operator*() const);T** location() const {return location_;}template <class S> static Handle<T> cast(Handle<S> that) {T::cast(*that);return Handle<T>(reinterpret_cast<T**>(that.location()));}static Handle<T> null() { return Handle<T>(); }bool is_null() {return location_ == NULL; }inline Handle<T> EscapeFrom(HandleScope* scope);private:T** location_;
};

下面是实现。

template<class T>
Handle<T>::Handle(T* obj) {location_ = reinterpret_cast<T**>(HandleScope::CreateHandle(obj));
}template <class T>
inline T* Handle<T>::operator*() const {return *location_;
}

Handle类的定义没有太多的逻辑,就是对用户定义的对象指针进行封装。有一个重要的地址是构造函数。我们看到当我们定义一个Handle的时候,他会调HandleScope::CreateHandle生成一个Handle对象。在HandleScope那篇文章已经分析过了。handle对象的location对象指针一个内存,该内存保存了obj的地址。

2 外部handle

// T表示handle管理的对象的类型
template <class T> class Handle {public:Handle();explicit Handle(T* val) : val_(val) { }// *that得到指向handle管理的对象的指针,转成T类型,赋值给val_template <class S> inline Handle(Handle<S> that): val_(reinterpret_cast<T*>(*that)) {TYPE_CHECK(T, S);}bool IsEmpty() { return val_ == 0; }T* operator->();T* operator*();void Clear() { this->val_ = 0; }/*比较handle指向的对象的地址是否相等this是指向当前对象的指针,*this是当前对象,**this是返回val_的值,看重载运算符*的实现*that是val_的值*/template <class S> bool operator==(Handle<S> that) {void** a = reinterpret_cast<void**>(**this);void** b = reinterpret_cast<void**>(*that);// a等于0,则返回b是否等于0,是的话说明a==b,即trueif (a == 0) return b == 0;// a不等于0,如果b==0,则返回falseif (b == 0) return false;// 比较ab,即取val_里的内容比较return *a == *b;}template <class S> bool operator!=(Handle<S> that) {return !operator==(that);}template <class S> static inline Handle<T> Cast(Handle<S> that) {// 返回一个空的handle,即val_是nullif (that.IsEmpty()) return Handle<T>();// *that得到指向handle管理的对象的指针,转成T类型的对象,转成底层对象是类型T的handlereturn Handle<T>(T::Cast(*that));}private:T* val_;
};

下面是实现。只有两个运算符的重载。

template <class T>
T* Handle<T>::operator->() {return val_;
}template <class T>
T* Handle<T>::operator*() {return val_;
}

我们看到Handle的实现没有太内容,就是在对象和用户之前加了一层。下面看看他的两个子类Local和Persistent。

3 Local

1 Local类是基于栈分配的一种Handle,他在一个函数开始的时候,声明一个HandleScope,HandleScope下面所有的Handle都在最近的的HandleScope中分配,函数执行完后,会一起被释放。

template <class T> class Local : public Handle<T> {public:Local();// 调用Local函数的时候S被替换成that对应的类型,结果是Handle底层的val_指向一个T类型的对象template <class S> inline Local(Local<S> that)// *that即取得他底层对象的地址: Handle<T>(reinterpret_cast<T*>(*that)) {TYPE_CHECK(T, S);}template <class S> inline Local(S* that) : Handle<T>(that) { }template <class S> static inline Local<T> Cast(Local<S> that) {if (that.IsEmpty()) return Local<T>();return Local<T>(T::Cast(*that));}static Local<T> New(Handle<T> that);
};

Local没有做什么事情,是对基类Handle的简单继承。下面是实现。

template <class T>
Handle<T>::Handle() : val_(0) { }template <class T>
Local<T>::Local() : Handle<T>() { }template <class T>
Local<T> Local<T>::New(Handle<T> that) {if (that.IsEmpty()) return Local<T>();void** p = reinterpret_cast<void**>(*that);return Local<T>(reinterpret_cast<T*>(HandleScope::CreateHandle(*p)));
}

我们看看如果使用一个句柄。

HandleScope scope;
Local<String> source = String::New('hello');

我们看一下String::New的实现。

// i::Handle表示内部使用的handle
Local<String> v8::String::New(const char* data, int length) {if (length == -1) length = strlen(data);// 申请一个对象,由handle管理i::Handle<i::String> result = i::Factory::NewStringFromUtf8(i::Vector<const char>(data, length));return Utils::ToLocal(result);
}Local<v8::String> Utils::ToLocal(v8::internal::Handle<v8::internal::String> obj) { return Local<String>(reinterpret_cast<String*>(obj.location())); 
}

我们在看下HandleScope中的那个图。再来例假ToLocal函数的逻辑。
在这里插入图片描述
我们对着图来理解ToLocal,我们知道obj.location()返回的是指针,指向保存了对象地址的内存地址。然后转成String*,即拿到对象的地址。构造一个Local对象返回。即Local内部管理用户定义的对象(String::New函数执行完后,他里面定义的result,即handle被析构)。如下图。
在这里插入图片描述
当HandleScope析构的时候,他会释放用户定义的对象的内存,然后Local对象本身是在栈上分配的,也会被析构。这就是v8用本地handle(临时handle)管理堆对象的大致原理。一般来说handle在函数结束后就会被释放,如果想在函数执行完还使得句柄可用,可用使用逃逸(escape)。原理是销毁当前的HandleScope,然后在前一个HandleScope对象里分配一个handle。下面继续看看持久句柄。
4 Persisten

template <class T> class Persistent : public Handle<T> {public:Persistent();template <class S> inline Persistent(Persistent<S> that): Handle<T>(reinterpret_cast<T*>(*that)) {TYPE_CHECK(T, S);}template <class S> inline Persistent(S* that) : Handle<T>(that) { }template <class S> explicit inline Persistent(Handle<S> that): Handle<T>(*that) { }template <class S> static inline Persistent<T> Cast(Persistent<S> that) 		{if (that.IsEmpty()) return Persistent<T>();return Persistent<T>(T::Cast(*that));}static Persistent<T> New(Handle<T> that);void Dispose();void MakeWeak(void* parameters, WeakReferenceCallback callback);void ClearWeak();bool IsNearDeath();bool IsWeak();private:friend class ImplementationUtilities;friend class ObjectTemplate;
};

相对于基类Handle,Persistent多了几个功能,我们看一下使用用例。

Persistent<Context> context = Context::New();

我们看一下Context::New()的定义。

Persistent<Context> v8::Context::New(v8::ExtensionConfiguration* extensions,v8::Handle<ObjectTemplate> global_template,v8::Handle<Value> global_object) {i::Handle<i::Context> env = i::Bootstrapper::CreateEnvironment(Utils::OpenHandle(*global_object),global_template, extensions);return Persistent<Context>(Utils::ToLocal(env));
}

我们看一下CreateEnvironment的实现。

Handle<Context> Bootstrapper::CreateEnvironment(...参数) {Genesis genesis(global_object, global_template, extensions);return genesis.result();
}Genesis::Genesis(...参数) {CreateRoots(global_template, global_object);result_ = global_context_;}void Genesis::CreateRoots(...参数) {// 创建一个全局上下文对象,分配一个Context对象global_context_ =Handle<Context>::cast(GlobalHandles::Create(*Factory::NewGlobalContext()));
}Handle<Context> result() { return result_; }

通过上面的代码我们知道Persistent指向的是一个GlobalHandles::Create返回的地址。所以我们主要分析GlobalHandles这个类的实现。这个类的代码比较多,我们只分析相关的(Node类维护一个对象的信息,地址,状态)。后面会单独分析。

// 一个handle对应一个Node
Handle<Object> GlobalHandles::Create(Object* value) {Counters::global_handles.Increment();Node* result;/*有一个free_list,保存着DESTROYED状态但还没有被释放的Node,first_free指向第一个节点,为NULL说明没有待回收的节点,即没有可重用的节点 */if (first_free() == NULL) {// Allocate a new node.// 没有可重用的节点则分配一个新的result = new Node(value);// 头插法,设置新增的node的下一个节点是当前头结点result->set_next(head());// 头指针指向新增的nodeset_head(result);} else {// Take the first node in the free list.// 获取一个可以重用的节点result = first_free();// 获取重用节点在free_list中的第一个节点,first_free指向新的可重用节点 set_first_free(result->next_free());// 重新初始化该节点result->Initialize(value);}// 返回Node对象的首地址return result->handle();
}

从上面的代码中我们大概知道,有一个链表,每个node节点保存了一个持久对象的信息。持久句柄指向的对象都是在这个链表里管理的。持久句柄执行node的地址,最后要调Dispose释放。

template <class T>
void Persistent<T>::Dispose() {if (this->IsEmpty()) return;V8::DisposeGlobal(reinterpret_cast<void**>(**this));
}void V8::DisposeGlobal(void** obj) {LOG_API("DisposeGlobal");if (has_shut_down) return;i::GlobalHandles::Destroy(reinterpret_cast<i::Object**>(obj));
}// 销毁一个节点
void GlobalHandles::Destroy(Object** location) {Counters::global_handles.Decrement();if (location == NULL) return;Node* node = Node::FromLocation(location);node->Destroy();// Link the destroyed.// 设置待销毁节点在free_list链表里的下一个节点是当前的头结点node->set_next_free(first_free());// 头指针指向待销毁的节点,set_first_free(node);
}

大致是根据对象的地址转成node节点,销毁该节点。从链表中删除。

总结,这就是v8中关于handle的一些知识。

这篇关于js引擎v8源码分析之Handle(基于v8 0.1.5)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/853446

相关文章

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

Redis中的有序集合zset从使用到原理分析

《Redis中的有序集合zset从使用到原理分析》Redis有序集合(zset)是字符串与分值的有序映射,通过跳跃表和哈希表结合实现高效有序性管理,适用于排行榜、延迟队列等场景,其时间复杂度低,内存占... 目录开篇:排行榜背后的秘密一、zset的基本使用1.1 常用命令1.2 Java客户端示例二、zse

Redis中的AOF原理及分析

《Redis中的AOF原理及分析》Redis的AOF通过记录所有写操作命令实现持久化,支持always/everysec/no三种同步策略,重写机制优化文件体积,与RDB结合可平衡数据安全与恢复效率... 目录开篇:从日记本到AOF一、AOF的基本执行流程1. 命令执行与记录2. AOF重写机制二、AOF的

JS纯前端实现浏览器语音播报、朗读功能的完整代码

《JS纯前端实现浏览器语音播报、朗读功能的完整代码》在现代互联网的发展中,语音技术正逐渐成为改变用户体验的重要一环,下面:本文主要介绍JS纯前端实现浏览器语音播报、朗读功能的相关资料,文中通过代码... 目录一、朗读单条文本:① 语音自选参数,按钮控制语音:② 效果图:二、朗读多条文本:① 语音有默认值:②

在Node.js中使用.env文件管理环境变量的全过程

《在Node.js中使用.env文件管理环境变量的全过程》Node.js应用程序通常依赖于环境变量来管理敏感信息或配置设置,.env文件已经成为一种流行的本地管理这些变量的方法,本文将探讨.env文件... 目录引言为什么使php用 .env 文件 ?如何在 Node.js 中使用 .env 文件最佳实践引

MyBatis Plus大数据量查询慢原因分析及解决

《MyBatisPlus大数据量查询慢原因分析及解决》大数据量查询慢常因全表扫描、分页不当、索引缺失、内存占用高及ORM开销,优化措施包括分页查询、流式读取、SQL优化、批处理、多数据源、结果集二次... 目录大数据量查询慢的常见原因优化方案高级方案配置调优监控与诊断总结大数据量查询慢的常见原因MyBAT

分析 Java Stream 的 peek使用实践与副作用处理方案

《分析JavaStream的peek使用实践与副作用处理方案》StreamAPI的peek操作是中间操作,用于观察元素但不终止流,其副作用风险包括线程安全、顺序混乱及性能问题,合理使用场景有限... 目录一、peek 操作的本质:有状态的中间操作二、副作用的定义与风险场景1. 并行流下的线程安全问题2. 顺

MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决

《MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决》MyBatis默认开启一级缓存,同一事务中循环调用查询方法时会重复使用缓存数据,导致获取的序列主键值均为1,... 目录问题原因解决办法如果是存储过程总结问题myBATis有如下代码获取序列作为主键IdMappe

使用Node.js和PostgreSQL构建数据库应用

《使用Node.js和PostgreSQL构建数据库应用》PostgreSQL是一个功能强大的开源关系型数据库,而Node.js是构建高效网络应用的理想平台,结合这两个技术,我们可以创建出色的数据驱动... 目录初始化项目与安装依赖建立数据库连接执行CRUD操作查询数据插入数据更新数据删除数据完整示例与最佳

Java中最全最基础的IO流概述和简介案例分析

《Java中最全最基础的IO流概述和简介案例分析》JavaIO流用于程序与外部设备的数据交互,分为字节流(InputStream/OutputStream)和字符流(Reader/Writer),处理... 目录IO流简介IO是什么应用场景IO流的分类流的超类类型字节文件流应用简介核心API文件输出流应用文