diff --git a/core/mmu.cc b/core/mmu.cc
index 6b3ed3cf1cf9f2855d29218d2316ab4c5c7416c8..800453603a8cd92a43133de87d4373d6142b3bfd 100644
--- a/core/mmu.cc
+++ b/core/mmu.cc
@@ -1079,15 +1079,28 @@ void jvm_balloon_vma::fault(uintptr_t fault_addr, exception_frame *ef)
 {
     std::lock_guard<mutex> guard(vma_list_mutex);
     jvm_balloon_fault(_balloon, ef, this);
+    delete this;
 }
 
-jvm_balloon_vma::~jvm_balloon_vma()
+void jvm_balloon_vma::detach_balloon()
 {
+    _balloon = nullptr;
     // Could block the creation of the next vma. No need to evacuate, we have no pages
     vma_list.erase(*this);
     mmu::map_anon(addr(), size(), _real_flags, _real_perm);
 }
 
+jvm_balloon_vma::~jvm_balloon_vma()
+{
+    if (_balloon) {
+        // This is because the JVM may just decide to unmap a whole region if
+        // it believes the objects are no longer valid. It could be the case
+        // for a dangling mapping representing a balloon that was already moved
+        // out.
+        jvm_balloon_fault(_balloon, nullptr, this);
+    }
+}
+
 // This function marks an anonymous vma as holding the JVM Heap. The JVM may
 // create mappings for a variety of reasons, not all of them being the heap.
 // Since we're interested in knowing how many pages does the heap hold (to make
diff --git a/include/osv/mmu.hh b/include/osv/mmu.hh
index 1ccb18c4ed7cfc7086ac7d2701510633bab24dfb..708540dfc43c24649218fb90f96c984fb1c52249 100644
--- a/include/osv/mmu.hh
+++ b/include/osv/mmu.hh
@@ -145,6 +145,7 @@ public:
     virtual void split(uintptr_t edge) override;
     virtual error sync(uintptr_t start, uintptr_t end) override;
     virtual void fault(uintptr_t addr, exception_frame *ef) override;
+    void detach_balloon();
 private:
     balloon *_balloon;
     unsigned _real_perm;
diff --git a/java/jvm_balloon.cc b/java/jvm_balloon.cc
index 2318195cbc8ca4e03cff565875fcbef928a42d79..14860a833b2085bf142be6a1a6c5707f3248670f 100644
--- a/java/jvm_balloon.cc
+++ b/java/jvm_balloon.cc
@@ -138,7 +138,7 @@ size_t balloon::move_balloon(unsigned char *dest, unsigned char *src)
 void finish_move(mmu::jvm_balloon_vma *vma)
 {
     unsigned char *addr = static_cast<unsigned char *>(vma->addr());
-    delete vma;
+    vma->detach_balloon();
     balloon_candidates.erase(addr);
 }
 
@@ -292,7 +292,7 @@ void jvm_balloon_fault(balloon *b, exception_frame *ef, mmu::jvm_balloon_vma *vm
     WITH_LOCK(balloons_lock) {
         assert(!balloons.empty());
 
-        if (ef->error_code == mmu::page_fault_write) {
+        if (!ef || (ef->error_code == mmu::page_fault_write)) {
             finish_move(vma);
             return;
         }