diff --git a/core/mempool.cc b/core/mempool.cc
index 919a9a67c12f1350ec2cad3fced14d32dece1def..04a1adb09283aa3b4b79e7a8a3db13071f7651a9 100644
--- a/core/mempool.cc
+++ b/core/mempool.cc
@@ -28,6 +28,7 @@
 #include <algorithm>
 #include <osv/prio.hh>
 #include <stdlib.h>
+#include <osv/shrinker.h>
 #include "java/jvm_balloon.hh"
 
 TRACEPOINT(trace_memory_malloc, "buf=%p, len=%d", void *, size_t);
@@ -629,6 +630,12 @@ shrinker::shrinker(std::string name)
     }
 }
 
+void *osv_register_shrinker(const char *name,
+                            size_t (*func)(size_t target, bool hard))
+{
+    return reinterpret_cast<void *>(new c_shrinker(name, func));
+}
+
 bool reclaimer_waiters::wake_waiters()
 {
     bool woken = false;
diff --git a/include/osv/shrinker.h b/include/osv/shrinker.h
new file mode 100644
index 0000000000000000000000000000000000000000..bac7d242b6d0ba685fbbc9a8a528e96a4fa1892d
--- /dev/null
+++ b/include/osv/shrinker.h
@@ -0,0 +1,28 @@
+#ifndef SHRINKER_H_
+#define SHRINKER_H_
+
+#ifdef __cplusplus
+
+#include <osv/mempool.hh>
+
+class c_shrinker : public memory::shrinker {
+public:
+    explicit c_shrinker(const char *name,
+                        size_t (*func)(size_t target, bool hard)) :
+            memory::shrinker(name), _func(func) {}
+    size_t request_memory(size_t s, bool hard) { return _func(s, hard); }
+private:
+    size_t (*_func)(size_t target, bool hard);
+};
+
+
+extern "C"
+void *osv_register_shrinker(const char *name,
+                            size_t (*func)(size_t target, bool hard));
+#else
+#include <stdbool.h>
+void *osv_register_shrinker(const char *name,
+                            size_t (*func)(size_t target, bool hard));
+#endif
+
+#endif