Objects in V8, for the types of handles that can hold them can be primarily: Local
or Persistent
. Though there’s another handle type: Eternal
which live for the lifetime of the Isolate
, thus never being garbage collected.
Local
represents a short lived object, from v8 header file itself: light-weight and transient and typically used in local operations. As soon as the HandleScope
managing this Local
handler is destroyed, the object wrapped is invalid, and eventually, garbage collected.
Persistent
handles, can be used to store objects across several execution units. These objects will eventually be garbage collected.
While embedding v8, I mostly always need to get a native object exposed in javascript, and this is done by pairing a Persistent
handler with a native object. On average, these native objects are created/destroyed as my javascript code flows, and its native object counterpart needs to be destroyed and freed accordingly. For this purpose, I set a weak handle callback
like:
class JavascriptWrapper { private: v8::Persistent<v8::Object> wrapper_; public: public void Wrap( v8::Isolate*, ... ) { ... // set weak handler wrapper_.SetWeak( this, weakCallbackForObjectHolder, v8::WeakCallbackType::kParameter); } // the weak handler function is as follows: static void weakCallbackForObjectHolder( const v8::WeakCallbackInfo<HC::Wrapper>& data) { delete data.GetParameter(); } }
This function will be invoked as soon as the javascript object is garbage collected, allowing me to reclaim all native side resources this object held.
Sometimes, I need to keep an object around until certain operations finish. For example, an Image object should be around until its async download process ends and gets the opportunity to notify its callbacks, avoiding garbage collection during the process. This is accomplished by tagging the Persistent
handle as not weak by calling:
wrapper_->ClearWeak();
This prevents the GC from reclaiming my object. Think of a javascript object like this:
const image = new Image(); image.addEventListener(“load”, (e)=> {...}); image.addEventListener(“error”, (e)=> {...}); image.src= 'http://...';
This code is likely to destroy the image
object before its callbacks had been notified. (Note thatimage
is not referenced at all, it is just defined and forgotten in javascript).
Since I expect either load
or error
callbacks to be notified, I must prevent GC from kicking in, and ClearWeak
does exactly that. Later, when the callbacks have been notified, I can natively flag the Persistent
handler available for garbage collection by calling SetWeak(...)
as in the example above. This ClearWeak
/SetWeak
combo gives me full control over my wrapped objects’ lifecycle.
Private References
There are some other times when I just need to bind the lifecycle of an object to another one. For example, a TouchEvent
contains a TouchList
object, and I want to bind their lifecycle together.
For this purpose, v8 also provides a Private property utility. As you can image, these properties will be inaccessible from javascript. To create a private property, just call:
v8::Local<v8::Value> v8Value= obj->Wrap(info.GetIsolate(), ...); // create a private property v8::Local<v8::Private> priv= v8::Private::ForApi( info.GetIsolate(), v8::String::NewFromUtf8( info.GetIsolate(), "KeepAlive#TouchEvent#ChangedTouches")); // assign this property to the object: info.Holder()->SetPrivate( info.GetIsolate()->GetCurrentContext(), priv, v8Value);
With this I just get an interesting effect, which is keep the TouchList object alive while the TouchEvent exists and no one can modify or break this bond from javascript.
There’s nonetheless another stage where my wrapped native objects deserve special attention, and this is at Isolate
destruction time.
Isolate destruction
Garbage collection must not be relied on to reclaim any object. In fact it might not fire during the javascript program lifecycle.
Under this premise, all native wrapped objects need a chance to be freed upon Isolate destruction. Specially if you expect to create another Isolate and avoid expensive memory leaks. How can we Identify our Persistent
handles for special treatment is done by tagging them with a call to:
wrapper_.SetWrapperClassId( int16_t_tag );
Later on, when the Isolate is being destroyed, I must do an explicit call to:
isolate_->VisitHandlesWithClassIds( &phv );
phv is an instance of a class like:
class PHV : public v8::PersistentHandleVisitor { public: v8::Isolate* isolate_; PHV(v8::Isolate* isolate) : isolate_(isolate) {} virtual ~PHV() {} virtual void VisitPersistentHandle( v8::Persistent<v8::Value>* value, uint16_t class_id) { // delete persistent handles on isolate disposal. if ( class_id==HC_GARBAGE_COLLECTED_CLASS_ID ) { v8::HandleScope hs(isolate_); Wrapper* w = // extract your wrapped object from // the passed-in value object. delete w; } } };
As you can see, handling native objects is actually pretty straightforward. Another demonstration of how delightful is to work with embedded v8.