diff --git a/src/node_util.cc b/src/node_util.cc
index 2a7d90cfe2b661..4cca0cbb72aed0 100644
--- a/src/node_util.cc
+++ b/src/node_util.cc
@@ -1,6 +1,7 @@
 #include "node_errors.h"
 #include "node_watchdog.h"
 #include "util.h"
+#include "base_object-inl.h"
 
 namespace node {
 namespace util {
@@ -11,6 +12,7 @@ using v8::Boolean;
 using v8::Context;
 using v8::Function;
 using v8::FunctionCallbackInfo;
+using v8::FunctionTemplate;
 using v8::IndexFilter;
 using v8::Integer;
 using v8::Isolate;
@@ -181,6 +183,37 @@ void EnqueueMicrotask(const FunctionCallbackInfo<Value>& args) {
   isolate->EnqueueMicrotask(args[0].As<Function>());
 }
 
+class WeakReference : public BaseObject {
+ public:
+  WeakReference(Environment* env, Local<Object> object, Local<Object> target)
+    : BaseObject(env, object) {
+    MakeWeak();
+    target_.Reset(env->isolate(), target);
+    target_.SetWeak();
+  }
+
+  static void New(const FunctionCallbackInfo<Value>& args) {
+    Environment* env = Environment::GetCurrent(args);
+    CHECK(args.IsConstructCall());
+    CHECK(args[0]->IsObject());
+    new WeakReference(env, args.This(), args[0].As<Object>());
+  }
+
+  static void Get(const FunctionCallbackInfo<Value>& args) {
+    WeakReference* weak_ref = Unwrap<WeakReference>(args.Holder());
+    Isolate* isolate = args.GetIsolate();
+    if (!weak_ref->target_.IsEmpty())
+      args.GetReturnValue().Set(weak_ref->target_.Get(isolate));
+  }
+
+  SET_MEMORY_INFO_NAME(WeakReference)
+  SET_SELF_SIZE(WeakReference)
+  SET_NO_MEMORY_INFO()
+
+ private:
+  Persistent<Object> target_;
+};
+
 void Initialize(Local<Object> target,
                 Local<Value> unused,
                 Local<Context> context,
@@ -241,6 +274,16 @@ void Initialize(Local<Object> target,
                   should_abort_on_uncaught_toggle,
                   env->should_abort_on_uncaught_toggle().GetJSArray())
             .FromJust());
+
+  Local<String> weak_ref_string =
+      FIXED_ONE_BYTE_STRING(env->isolate(), "WeakReference");
+  Local<FunctionTemplate> weak_ref =
+      env->NewFunctionTemplate(WeakReference::New);
+  weak_ref->InstanceTemplate()->SetInternalFieldCount(1);
+  weak_ref->SetClassName(weak_ref_string);
+  env->SetProtoMethod(weak_ref, "get", WeakReference::Get);
+  target->Set(context, weak_ref_string,
+              weak_ref->GetFunction(context).ToLocalChecked()).FromJust();
 }
 
 }  // namespace util
diff --git a/test/parallel/test-internal-util-weakreference.js b/test/parallel/test-internal-util-weakreference.js
new file mode 100644
index 00000000000000..b48b34fe2309ea
--- /dev/null
+++ b/test/parallel/test-internal-util-weakreference.js
@@ -0,0 +1,17 @@
+// Flags: --expose-internals --expose-gc
+'use strict';
+require('../common');
+const assert = require('assert');
+const { internalBinding } = require('internal/test/binding');
+const { WeakReference } = internalBinding('util');
+
+let obj = { hello: 'world' };
+const ref = new WeakReference(obj);
+assert.strictEqual(ref.get(), obj);
+
+setImmediate(() => {
+  obj = null;
+  global.gc();
+
+  assert.strictEqual(ref.get(), undefined);
+});