diff --git a/src/google/protobuf/map.h b/src/google/protobuf/map.h
index 30481dbd64..29b9bc1d16 100644
--- a/src/google/protobuf/map.h
+++ b/src/google/protobuf/map.h
@@ -806,15 +806,23 @@ inline void UntypedMapIterator::SearchFrom(size_t start_bucket) {
 // code, since that would bring in Message too.
 class MapFieldBaseForParse {
  public:
-  const UntypedMapBase& GetMap() const { return GetMapImpl(false); }
+  const UntypedMapBase& GetMap() const {
+    return vtable_->get_map(*this, false);
+  }
   UntypedMapBase* MutableMap() {
-    return &const_cast<UntypedMapBase&>(GetMapImpl(true));
+    return &const_cast<UntypedMapBase&>(vtable_->get_map(*this, true));
   }
 
  protected:
+  struct VTable {
+    const UntypedMapBase& (*get_map)(const MapFieldBaseForParse&,
+                                     bool is_mutable);
+  };
+  explicit constexpr MapFieldBaseForParse(const VTable* vtable)
+      : vtable_(vtable) {}
   ~MapFieldBaseForParse() = default;
 
-  virtual const UntypedMapBase& GetMapImpl(bool is_mutable) const = 0;
+  const VTable* vtable_;
 };
 
 // The value might be of different signedness, so use memcpy to extract it.
diff --git a/src/google/protobuf/map_field.cc b/src/google/protobuf/map_field.cc
index 76d065baef..0d70c65301 100644
--- a/src/google/protobuf/map_field.cc
+++ b/src/google/protobuf/map_field.cc
@@ -10,6 +10,8 @@
 #include <utility>
 #include <vector>
 
+#include "absl/log/absl_check.h"
+#include "google/protobuf/map.h"
 #include "google/protobuf/map_field_inl.h"
 #include "google/protobuf/port.h"
 
@@ -46,10 +48,12 @@ MapFieldBase::~MapFieldBase() {
   delete maybe_payload();
 }
 
-const UntypedMapBase& MapFieldBase::GetMapImpl(bool is_mutable) const {
-  SyncMapWithRepeatedField();
-  if (is_mutable) const_cast<MapFieldBase*>(this)->SetMapDirty();
-  return GetMapRaw();
+const UntypedMapBase& MapFieldBase::GetMapImpl(const MapFieldBaseForParse& map,
+                                               bool is_mutable) {
+  const auto& self = static_cast<const MapFieldBase&>(map);
+  self.SyncMapWithRepeatedField();
+  if (is_mutable) const_cast<MapFieldBase&>(self).SetMapDirty();
+  return self.GetMapRaw();
 }
 
 void MapFieldBase::MapBegin(MapIterator* map_iter) const {
@@ -121,24 +125,24 @@ MapFieldBase::ReflectionPayload& MapFieldBase::PayloadSlow() const {
   return *ToPayload(p);
 }
 
-void MapFieldBase::Swap(MapFieldBase* other) {
-  if (arena() == other->arena()) {
-    InternalSwap(other);
+void MapFieldBase::SwapImpl(MapFieldBase& lhs, MapFieldBase& rhs) {
+  if (lhs.arena() == rhs.arena()) {
+    lhs.InternalSwap(&rhs);
     return;
   }
-  auto* p1 = maybe_payload();
-  auto* p2 = other->maybe_payload();
+  auto* p1 = lhs.maybe_payload();
+  auto* p2 = rhs.maybe_payload();
   if (p1 == nullptr && p2 == nullptr) return;
 
-  if (p1 == nullptr) p1 = &payload();
-  if (p2 == nullptr) p2 = &other->payload();
+  if (p1 == nullptr) p1 = &lhs.payload();
+  if (p2 == nullptr) p2 = &rhs.payload();
   p1->repeated_field.Swap(&p2->repeated_field);
   SwapRelaxed(p1->state, p2->state);
 }
 
-void MapFieldBase::UnsafeShallowSwap(MapFieldBase* other) {
-  ABSL_DCHECK_EQ(arena(), other->arena());
-  InternalSwap(other);
+void MapFieldBase::UnsafeShallowSwapImpl(MapFieldBase& lhs, MapFieldBase& rhs) {
+  ABSL_DCHECK_EQ(lhs.arena(), rhs.arena());
+  lhs.InternalSwap(&rhs);
 }
 
 void MapFieldBase::InternalSwap(MapFieldBase* other) {
@@ -158,14 +162,6 @@ size_t MapFieldBase::SpaceUsedExcludingSelfLong() const {
   return size;
 }
 
-size_t MapFieldBase::SpaceUsedExcludingSelfNoLock() const {
-  if (auto* p = maybe_payload()) {
-    return p->repeated_field.SpaceUsedExcludingSelfLong();
-  } else {
-    return 0;
-  }
-}
-
 bool MapFieldBase::IsMapValid() const {
   ConstAccess();
   // "Acquire" insures the operation after SyncRepeatedFieldWithMap won't get
@@ -402,12 +398,16 @@ bool MapFieldBase::InsertOrLookupMapValue(const MapKey& map_key,
 
 // ------------------DynamicMapField------------------
 DynamicMapField::DynamicMapField(const Message* default_entry)
-    : default_entry_(default_entry) {}
+    : DynamicMapField::TypeDefinedMapFieldBase(&kVTable),
+      default_entry_(default_entry) {}
 
 DynamicMapField::DynamicMapField(const Message* default_entry, Arena* arena)
-    : TypeDefinedMapFieldBase<MapKey, MapValueRef>(arena),
+    : TypeDefinedMapFieldBase<MapKey, MapValueRef>(&kVTable, arena),
       default_entry_(default_entry) {}
 
+constexpr DynamicMapField::VTable DynamicMapField::kVTable =
+    MakeVTable<DynamicMapField>();
+
 DynamicMapField::~DynamicMapField() {
   ABSL_DCHECK_EQ(arena(), nullptr);
   // DynamicMapField owns map values. Need to delete them before clearing the
@@ -418,14 +418,15 @@ DynamicMapField::~DynamicMapField() {
   map_.clear();
 }
 
-void DynamicMapField::ClearMapNoSync() {
-  if (arena() == nullptr) {
-    for (auto& elem : map_) {
+void DynamicMapField::ClearMapNoSyncImpl(MapFieldBase& base) {
+  auto& self = static_cast<DynamicMapField&>(base);
+  if (self.arena() == nullptr) {
+    for (auto& elem : self.map_) {
       elem.second.DeleteData();
     }
   }
 
-  map_.clear();
+  self.map_.clear();
 }
 
 void DynamicMapField::AllocateMapValue(MapValueRef* map_val) {
@@ -460,12 +461,14 @@ void DynamicMapField::AllocateMapValue(MapValueRef* map_val) {
   }
 }
 
-bool DynamicMapField::InsertOrLookupMapValueNoSync(const MapKey& map_key,
-                                                   MapValueRef* val) {
-  Map<MapKey, MapValueRef>::iterator iter = map_.find(map_key);
-  if (iter == map_.end()) {
-    MapValueRef& map_val = map_[map_key];
-    AllocateMapValue(&map_val);
+bool DynamicMapField::InsertOrLookupMapValueNoSyncImpl(MapFieldBase& base,
+                                                       const MapKey& map_key,
+                                                       MapValueRef* val) {
+  auto& self = static_cast<DynamicMapField&>(base);
+  Map<MapKey, MapValueRef>::iterator iter = self.map_.find(map_key);
+  if (iter == self.map_.end()) {
+    MapValueRef& map_val = self.map_[map_key];
+    self.AllocateMapValue(&map_val);
     val->CopyFrom(map_val);
     return true;
   }
@@ -475,9 +478,11 @@ bool DynamicMapField::InsertOrLookupMapValueNoSync(const MapKey& map_key,
   return false;
 }
 
-void DynamicMapField::MergeFrom(const MapFieldBase& other) {
-  ABSL_DCHECK(IsMapValid() && other.IsMapValid());
-  Map<MapKey, MapValueRef>* map = MutableMap();
+void DynamicMapField::MergeFromImpl(MapFieldBase& base,
+                                    const MapFieldBase& other) {
+  auto& self = static_cast<DynamicMapField&>(base);
+  ABSL_DCHECK(self.IsMapValid() && other.IsMapValid());
+  Map<MapKey, MapValueRef>* map = self.MutableMap();
   const DynamicMapField& other_field =
       reinterpret_cast<const DynamicMapField&>(other);
   for (Map<MapKey, MapValueRef>::const_iterator other_it =
@@ -486,15 +491,15 @@ void DynamicMapField::MergeFrom(const MapFieldBase& other) {
     Map<MapKey, MapValueRef>::iterator iter = map->find(other_it->first);
     MapValueRef* map_val;
     if (iter == map->end()) {
-      map_val = &map_[other_it->first];
-      AllocateMapValue(map_val);
+      map_val = &self.map_[other_it->first];
+      self.AllocateMapValue(map_val);
     } else {
       map_val = &iter->second;
     }
 
     // Copy map value
     const FieldDescriptor* field_descriptor =
-        default_entry_->GetDescriptor()->map_value();
+        self.default_entry_->GetDescriptor()->map_value();
     switch (field_descriptor->cpp_type()) {
       case FieldDescriptor::CPPTYPE_INT32: {
         map_val->SetInt32Value(other_it->second.GetInt32Value());
@@ -541,17 +546,20 @@ void DynamicMapField::MergeFrom(const MapFieldBase& other) {
   }
 }
 
-const Message* DynamicMapField::GetPrototype() const { return default_entry_; }
+const Message* DynamicMapField::GetPrototypeImpl(const MapFieldBase& map) {
+  return static_cast<const DynamicMapField&>(map).default_entry_;
+}
 
-size_t DynamicMapField::SpaceUsedExcludingSelfNoLock() const {
+size_t DynamicMapField::SpaceUsedExcludingSelfNoLockImpl(
+    const MapFieldBase& map) {
+  auto& self = static_cast<const DynamicMapField&>(map);
   size_t size = 0;
-  if (auto* p = maybe_payload()) {
+  if (auto* p = self.maybe_payload()) {
     size += p->repeated_field.SpaceUsedExcludingSelfLong();
   }
-  size += sizeof(map_);
-  size_t map_size = map_.size();
+  size_t map_size = self.map_.size();
   if (map_size) {
-    Map<MapKey, MapValueRef>::const_iterator it = map_.begin();
+    Map<MapKey, MapValueRef>::const_iterator it = self.map_.begin();
     size += sizeof(it->first) * map_size;
     size += sizeof(it->second) * map_size;
     // If key is string, add the allocated space.
@@ -576,7 +584,7 @@ size_t DynamicMapField::SpaceUsedExcludingSelfNoLock() const {
       HANDLE_TYPE(ENUM, int32_t);
 #undef HANDLE_TYPE
       case FieldDescriptor::CPPTYPE_MESSAGE: {
-        while (it != map_.end()) {
+        while (it != self.map_.end()) {
           const Message& message = it->second.GetMessageValue();
           size += message.GetReflection()->SpaceUsedLong(message);
           ++it;
diff --git a/src/google/protobuf/map_field.h b/src/google/protobuf/map_field.h
index 86f1eeee29..8aa9e45fe3 100644
--- a/src/google/protobuf/map_field.h
+++ b/src/google/protobuf/map_field.h
@@ -18,6 +18,7 @@
 #include "google/protobuf/generated_message_reflection.h"
 #include "google/protobuf/generated_message_util.h"
 #include "google/protobuf/internal_visibility.h"
+#include "google/protobuf/map.h"
 #include "google/protobuf/map_entry.h"
 #include "google/protobuf/map_field_lite.h"
 #include "google/protobuf/map_type_handler.h"
@@ -295,8 +296,10 @@ class MapFieldAccessor;
 // reflection implementation only. Users should never use this directly.
 class PROTOBUF_EXPORT MapFieldBase : public MapFieldBaseForParse {
  public:
-  constexpr MapFieldBase() {}
-  explicit MapFieldBase(Arena* arena) : payload_{ToTaggedPtr(arena)} {}
+  explicit constexpr MapFieldBase(const VTable* vtable)
+      : MapFieldBaseForParse(vtable) {}
+  explicit MapFieldBase(const VTable* vtable, Arena* arena)
+      : MapFieldBaseForParse(vtable), payload_{ToTaggedPtr(arena)} {}
   MapFieldBase(const MapFieldBase&) = delete;
   MapFieldBase& operator=(const MapFieldBase&) = delete;
 
@@ -304,6 +307,39 @@ class PROTOBUF_EXPORT MapFieldBase : public MapFieldBaseForParse {
   // "protected" stops users from deleting a `MapFieldBase *`
   ~MapFieldBase();
 
+  struct VTable : MapFieldBaseForParse::VTable {
+    bool (*lookup_map_value)(const MapFieldBase& map, const MapKey& map_key,
+                             MapValueConstRef* val);
+    bool (*delete_map_value)(MapFieldBase& map, const MapKey& map_key);
+    void (*set_map_iterator_value)(MapIterator* map_iter);
+    bool (*insert_or_lookup_no_sync)(MapFieldBase& map, const MapKey& map_key,
+                                     MapValueRef* val);
+
+    void (*clear_map_no_sync)(MapFieldBase& map);
+    void (*merge_from)(MapFieldBase& map, const MapFieldBase& other);
+    void (*swap)(MapFieldBase& lhs, MapFieldBase& rhs);
+    void (*unsafe_shallow_swap)(MapFieldBase& lhs, MapFieldBase& rhs);
+    size_t (*space_used_excluding_self_nolock)(const MapFieldBase& map);
+
+    const Message* (*get_prototype)(const MapFieldBase& map);
+  };
+  template <typename T>
+  static constexpr VTable MakeVTable() {
+    VTable out{};
+    out.get_map = &T::GetMapImpl;
+    out.lookup_map_value = &T::LookupMapValueImpl;
+    out.delete_map_value = &T::DeleteMapValueImpl;
+    out.set_map_iterator_value = &T::SetMapIteratorValueImpl;
+    out.insert_or_lookup_no_sync = &T::InsertOrLookupMapValueNoSyncImpl;
+    out.clear_map_no_sync = &T::ClearMapNoSyncImpl;
+    out.merge_from = &T::MergeFromImpl;
+    out.swap = &T::SwapImpl;
+    out.unsafe_shallow_swap = &T::UnsafeShallowSwapImpl;
+    out.space_used_excluding_self_nolock = &T::SpaceUsedExcludingSelfNoLockImpl;
+    out.get_prototype = &T::GetPrototypeImpl;
+    return out;
+  }
+
  public:
   // Returns reference to internal repeated field. Data written using
   // Map's api prior to calling this function is guarantted to be
@@ -313,10 +349,14 @@ class PROTOBUF_EXPORT MapFieldBase : public MapFieldBaseForParse {
   // Like above. Returns mutable pointer to the internal repeated field.
   RepeatedPtrFieldBase* MutableRepeatedField();
 
-  // Pure virtual map APIs for Map Reflection.
-  virtual bool ContainsMapKey(const MapKey& map_key) const = 0;
-  virtual bool LookupMapValue(const MapKey& map_key,
-                              MapValueConstRef* val) const = 0;
+  const VTable* vtable() const { return static_cast<const VTable*>(vtable_); }
+
+  bool ContainsMapKey(const MapKey& map_key) const {
+    return LookupMapValue(map_key, static_cast<MapValueConstRef*>(nullptr));
+  }
+  bool LookupMapValue(const MapKey& map_key, MapValueConstRef* val) const {
+    return vtable()->lookup_map_value(*this, map_key, val);
+  }
   bool LookupMapValue(const MapKey&, MapValueRef*) const = delete;
 
   bool InsertOrLookupMapValue(const MapKey& map_key, MapValueRef* val);
@@ -325,19 +365,26 @@ class PROTOBUF_EXPORT MapFieldBase : public MapFieldBaseForParse {
   bool IsRepeatedFieldValid() const;
   // Insures operations after won't get executed before calling this.
   bool IsMapValid() const;
-  virtual bool DeleteMapValue(const MapKey& map_key) = 0;
-  virtual void MergeFrom(const MapFieldBase& other) = 0;
-  virtual void Swap(MapFieldBase* other);
-  virtual void UnsafeShallowSwap(MapFieldBase* other);
+  bool DeleteMapValue(const MapKey& map_key) {
+    return vtable()->delete_map_value(*this, map_key);
+  }
+  void MergeFrom(const MapFieldBase& other) {
+    vtable()->merge_from(*this, other);
+  }
+  void Swap(MapFieldBase* other) { vtable()->swap(*this, *other); }
+  void UnsafeShallowSwap(MapFieldBase* other) {
+    vtable()->unsafe_shallow_swap(*this, *other);
+  }
   // Sync Map with repeated field and returns the size of map.
   int size() const;
   void Clear();
-  virtual void SetMapIteratorValue(MapIterator* map_iter) const = 0;
+  void SetMapIteratorValue(MapIterator* map_iter) const {
+    return vtable()->set_map_iterator_value(map_iter);
+  }
 
   void MapBegin(MapIterator* map_iter) const;
   void MapEnd(MapIterator* map_iter) const;
   bool EqualIterator(const MapIterator& a, const MapIterator& b) const;
-  const UntypedMapBase& GetMapImpl(bool is_mutable) const final;
 
   // Returns the number of bytes used by the repeated field, excluding
   // sizeof(*this)
@@ -349,10 +396,12 @@ class PROTOBUF_EXPORT MapFieldBase : public MapFieldBaseForParse {
 
  protected:
   // Gets the size of space used by map field.
-  virtual size_t SpaceUsedExcludingSelfNoLock() const;
+  size_t SpaceUsedExcludingSelfNoLock() const {
+    return vtable()->space_used_excluding_self_nolock(*this);
+  }
 
-  virtual const Message* GetPrototype() const = 0;
-  virtual void ClearMapNoSync() = 0;
+  const Message* GetPrototype() const { return vtable()->get_prototype(*this); }
+  void ClearMapNoSync() { return vtable()->clear_map_no_sync(*this); }
 
   // Synchronizes the content in Map to RepeatedPtrField if there is any change
   // to Map after last synchronization.
@@ -364,6 +413,9 @@ class PROTOBUF_EXPORT MapFieldBase : public MapFieldBaseForParse {
   void SyncMapWithRepeatedField() const;
   void SyncMapWithRepeatedFieldNoLock();
 
+  static void SwapImpl(MapFieldBase& lhs, MapFieldBase& rhs);
+  static void UnsafeShallowSwapImpl(MapFieldBase& lhs, MapFieldBase& rhs);
+
   // Tells MapFieldBase that there is new change to Map.
   void SetMapDirty();
 
@@ -373,8 +425,9 @@ class PROTOBUF_EXPORT MapFieldBase : public MapFieldBaseForParse {
   // Provides derived class the access to repeated field.
   void* MutableRepeatedPtrField() const;
 
-  virtual bool InsertOrLookupMapValueNoSync(const MapKey& map_key,
-                                            MapValueRef* val) = 0;
+  bool InsertOrLookupMapValueNoSync(const MapKey& map_key, MapValueRef* val) {
+    return vtable()->insert_or_lookup_no_sync(*this, map_key, val);
+  }
 
   void InternalSwap(MapFieldBase* other);
 
@@ -439,6 +492,9 @@ class PROTOBUF_EXPORT MapFieldBase : public MapFieldBaseForParse {
                         : STATE_MODIFIED_MAP;
   }
 
+  static const UntypedMapBase& GetMapImpl(const MapFieldBaseForParse& map,
+                                          bool is_mutable);
+
  private:
   friend class ContendedMapCleanTest;
   friend class GeneratedMessageReflection;
@@ -501,7 +557,8 @@ class PROTOBUF_EXPORT MapFieldBase : public MapFieldBaseForParse {
 template <typename Key, typename T>
 class TypeDefinedMapFieldBase : public MapFieldBase {
  public:
-  constexpr TypeDefinedMapFieldBase() : map_() {
+  explicit constexpr TypeDefinedMapFieldBase(const VTable* vtable)
+      : MapFieldBase(vtable), map_() {
     // This invariant is required by MapFieldBase to easily access the map
     // member without paying for dynamic dispatch. It reduces code size.
     static_assert(PROTOBUF_FIELD_OFFSET(TypeDefinedMapFieldBase, map_) ==
@@ -511,10 +568,8 @@ class TypeDefinedMapFieldBase : public MapFieldBase {
   TypeDefinedMapFieldBase(const TypeDefinedMapFieldBase&) = delete;
   TypeDefinedMapFieldBase& operator=(const TypeDefinedMapFieldBase&) = delete;
 
-  explicit TypeDefinedMapFieldBase(Arena* arena)
-      : MapFieldBase(arena), map_(arena) {}
-  TypeDefinedMapFieldBase(ArenaInitialized, Arena* arena)
-      : TypeDefinedMapFieldBase(arena) {}
+  TypeDefinedMapFieldBase(const VTable* vtable, Arena* arena)
+      : MapFieldBase(vtable, arena), map_(arena) {}
 
  protected:
   ~TypeDefinedMapFieldBase() { map_.~Map(); }
@@ -535,33 +590,36 @@ class TypeDefinedMapFieldBase : public MapFieldBase {
     return &map_;
   }
 
-  void ClearMapNoSync() override { map_.clear(); }
+  static void ClearMapNoSyncImpl(MapFieldBase& map) {
+    static_cast<TypeDefinedMapFieldBase&>(map).map_.clear();
+  }
 
   void InternalSwap(TypeDefinedMapFieldBase* other);
 
-  void Swap(MapFieldBase* other) final;
-
-  void UnsafeShallowSwap(MapFieldBase* other) override;
-  size_t SpaceUsedExcludingSelfNoLock() const override;
-  void MergeFrom(const MapFieldBase& other) override;
-
  protected:
+  friend struct MapFieldTestPeer;
+
   using Iter = typename Map<Key, T>::const_iterator;
 
+  static bool DeleteMapValueImpl(MapFieldBase& map, const MapKey& map_key);
+  static bool LookupMapValueImpl(const MapFieldBase& self,
+                                 const MapKey& map_key, MapValueConstRef* val);
+  static void SetMapIteratorValueImpl(MapIterator* map_iter);
+  static bool InsertOrLookupMapValueNoSyncImpl(MapFieldBase& map,
+                                               const MapKey& map_key,
+                                               MapValueRef* val);
+
+  static void MergeFromImpl(MapFieldBase& base, const MapFieldBase& other);
+  static void SwapImpl(MapFieldBase& lhs, MapFieldBase& rhs);
+  static void UnsafeShallowSwapImpl(MapFieldBase& lhs, MapFieldBase& rhs);
+
+  static size_t SpaceUsedExcludingSelfNoLockImpl(const MapFieldBase& map);
+
   // map_ is inside an anonymous union so we can explicitly control its
   // destruction
   union {
     Map<Key, T> map_;
   };
-
- private:
-  void SetMapIteratorValue(MapIterator* map_iter) const final;
-  bool ContainsMapKey(const MapKey& map_key) const final;
-  bool LookupMapValue(const MapKey& map_key, MapValueConstRef* val) const final;
-  bool LookupMapValue(const MapKey&, MapValueRef*) const = delete;
-  bool DeleteMapValue(const MapKey& map_key) final;
-  bool InsertOrLookupMapValueNoSync(const MapKey& map_key,
-                                    MapValueRef* val) override;
 };
 
 // This class provides access to map field using generated api. It is used for
@@ -584,19 +642,18 @@ class MapField final : public TypeDefinedMapFieldBase<Key, T> {
   static constexpr WireFormatLite::FieldType kKeyFieldType = kKeyFieldType_;
   static constexpr WireFormatLite::FieldType kValueFieldType = kValueFieldType_;
 
-  constexpr MapField() {}
+  constexpr MapField() : MapField::TypeDefinedMapFieldBase(&kVTable) {}
   MapField(const MapField&) = delete;
   MapField& operator=(const MapField&) = delete;
   ~MapField() {}
 
-  explicit MapField(Arena* arena) : TypeDefinedMapFieldBase<Key, T>(arena) {}
+  explicit MapField(Arena* arena)
+      : TypeDefinedMapFieldBase<Key, T>(&kVTable, arena) {}
   MapField(ArenaInitialized, Arena* arena) : MapField(arena) {}
-
-  MapField(InternalVisibility, Arena* arena)
-      : TypeDefinedMapFieldBase<Key, T>(arena) {}
+  MapField(InternalVisibility, Arena* arena) : MapField(arena) {}
   MapField(InternalVisibility, Arena* arena, const MapField& from)
-      : TypeDefinedMapFieldBase<Key, T>(arena) {
-    TypeDefinedMapFieldBase<Key, T>::MergeFrom(from);
+      : MapField(arena) {
+    this->MergeFromImpl(*this, from);
   }
 
   // Used in the implementation of parsing. Caller should take the ownership iff
@@ -609,13 +666,22 @@ class MapField final : public TypeDefinedMapFieldBase<Key, T> {
   typedef void InternalArenaConstructable_;
   typedef void DestructorSkippable_;
 
-  // Implements MapFieldBase
-  const Message* GetPrototype() const final;
+  static const Message* GetPrototypeImpl(const MapFieldBase& map);
+
+  static const MapFieldBase::VTable kVTable;
 
   friend class google::protobuf::Arena;
+  friend class MapFieldBase;
   friend class MapFieldStateTest;  // For testing, it needs raw access to impl_
 };
 
+template <typename Derived, typename Key, typename T,
+          WireFormatLite::FieldType kKeyFieldType_,
+          WireFormatLite::FieldType kValueFieldType_>
+constexpr MapFieldBase::VTable
+    MapField<Derived, Key, T, kKeyFieldType_, kValueFieldType_>::kVTable =
+        MapField::template MakeVTable<MapField>();
+
 template <typename Key, typename T>
 bool AllAreInitialized(const TypeDefinedMapFieldBase<Key, T>& field) {
   for (const auto& p : field.GetMap()) {
@@ -639,24 +705,31 @@ class PROTOBUF_EXPORT DynamicMapField final
   DynamicMapField(const Message* default_entry, Arena* arena);
   DynamicMapField(const DynamicMapField&) = delete;
   DynamicMapField& operator=(const DynamicMapField&) = delete;
-  virtual ~DynamicMapField();
-
-  // Implement MapFieldBase
-  bool InsertOrLookupMapValueNoSync(const MapKey& map_key,
-                                    MapValueRef* val) final;
-  void MergeFrom(const MapFieldBase& other) final;
-  void UnsafeShallowSwap(MapFieldBase* other) final { Swap(other); }
-
-  void ClearMapNoSync() final;
+  ~DynamicMapField();
 
  private:
+  friend class MapFieldBase;
+
   const Message* default_entry_;
 
+  static const VTable kVTable;
+
   void AllocateMapValue(MapValueRef* map_val);
 
-  // Implements MapFieldBase
-  const Message* GetPrototype() const final;
-  size_t SpaceUsedExcludingSelfNoLock() const final;
+  static void MergeFromImpl(MapFieldBase& base, const MapFieldBase& other);
+  static bool InsertOrLookupMapValueNoSyncImpl(MapFieldBase& base,
+                                               const MapKey& map_key,
+                                               MapValueRef* val);
+  static void ClearMapNoSyncImpl(MapFieldBase& base);
+
+  static void UnsafeShallowSwapImpl(MapFieldBase& lhs, MapFieldBase& rhs) {
+    static_cast<DynamicMapField&>(lhs).Swap(
+        static_cast<DynamicMapField*>(&rhs));
+  }
+
+  static size_t SpaceUsedExcludingSelfNoLockImpl(const MapFieldBase& map);
+
+  static const Message* GetPrototypeImpl(const MapFieldBase& map);
 };
 
 }  // namespace internal
diff --git a/src/google/protobuf/map_field_inl.h b/src/google/protobuf/map_field_inl.h
index 6a1fc8a6fe..499d8ed2ec 100644
--- a/src/google/protobuf/map_field_inl.h
+++ b/src/google/protobuf/map_field_inl.h
@@ -8,6 +8,7 @@
 #ifndef GOOGLE_PROTOBUF_MAP_FIELD_INL_H__
 #define GOOGLE_PROTOBUF_MAP_FIELD_INL_H__
 
+#include <cstddef>
 #include <memory>
 #include <string>
 #include <tuple>
@@ -17,6 +18,7 @@
 #include "google/protobuf/map.h"
 #include "google/protobuf/map_field.h"
 #include "google/protobuf/map_type_handler.h"
+#include "google/protobuf/message.h"
 #include "google/protobuf/port.h"
 
 // must be last
@@ -86,8 +88,8 @@ inline void SetMapKey(MapKey* map_key, const MapKey& value) {
 
 // ------------------------TypeDefinedMapFieldBase---------------
 template <typename Key, typename T>
-void TypeDefinedMapFieldBase<Key, T>::SetMapIteratorValue(
-    MapIterator* map_iter) const {
+void TypeDefinedMapFieldBase<Key, T>::SetMapIteratorValueImpl(
+    MapIterator* map_iter) {
   if (map_iter->iter_.Equals(UntypedMapBase::EndIterator())) return;
   auto iter = typename Map<Key, T>::const_iterator(map_iter->iter_);
   SetMapKey(&map_iter->key_, iter->first);
@@ -95,56 +97,60 @@ void TypeDefinedMapFieldBase<Key, T>::SetMapIteratorValue(
 }
 
 template <typename Key, typename T>
-bool TypeDefinedMapFieldBase<Key, T>::ContainsMapKey(
-    const MapKey& map_key) const {
-  return GetMap().contains(UnwrapMapKey<Key>(map_key));
-}
-
-template <typename Key, typename T>
-bool TypeDefinedMapFieldBase<Key, T>::InsertOrLookupMapValueNoSync(
-    const MapKey& map_key, MapValueRef* val) {
-  auto res = map_.try_emplace(UnwrapMapKey<Key>(map_key));
+bool TypeDefinedMapFieldBase<Key, T>::InsertOrLookupMapValueNoSyncImpl(
+    MapFieldBase& map, const MapKey& map_key, MapValueRef* val) {
+  auto res = static_cast<TypeDefinedMapFieldBase&>(map).map_.try_emplace(
+      UnwrapMapKey<Key>(map_key));
   val->SetValue(&res.first->second);
   return res.second;
 }
 
 template <typename Key, typename T>
-bool TypeDefinedMapFieldBase<Key, T>::LookupMapValue(
-    const MapKey& map_key, MapValueConstRef* val) const {
-  const auto& map = GetMap();
+bool TypeDefinedMapFieldBase<Key, T>::LookupMapValueImpl(
+    const MapFieldBase& self, const MapKey& map_key, MapValueConstRef* val) {
+  const auto& map = static_cast<const TypeDefinedMapFieldBase&>(self).GetMap();
   auto iter = map.find(UnwrapMapKey<Key>(map_key));
   if (map.end() == iter) {
     return false;
   }
-  val->SetValueOrCopy(&iter->second);
+  if (val != nullptr) {
+    val->SetValueOrCopy(&iter->second);
+  }
   return true;
 }
 
 template <typename Key, typename T>
-bool TypeDefinedMapFieldBase<Key, T>::DeleteMapValue(const MapKey& map_key) {
-  return MutableMap()->erase(UnwrapMapKey<Key>(map_key));
+bool TypeDefinedMapFieldBase<Key, T>::DeleteMapValueImpl(
+    MapFieldBase& map, const MapKey& map_key) {
+  return static_cast<TypeDefinedMapFieldBase&>(map).MutableMap()->erase(
+      UnwrapMapKey<Key>(map_key));
 }
 
 template <typename Key, typename T>
-void TypeDefinedMapFieldBase<Key, T>::Swap(MapFieldBase* other) {
-  MapFieldBase::Swap(other);
-  auto* other_field = DownCast<TypeDefinedMapFieldBase*>(other);
-  map_.swap(other_field->map_);
+void TypeDefinedMapFieldBase<Key, T>::SwapImpl(MapFieldBase& lhs,
+                                               MapFieldBase& rhs) {
+  MapFieldBase::SwapImpl(lhs, rhs);
+  static_cast<TypeDefinedMapFieldBase&>(lhs).map_.swap(
+      static_cast<TypeDefinedMapFieldBase&>(rhs).map_);
 }
 
 template <typename Key, typename T>
-void TypeDefinedMapFieldBase<Key, T>::MergeFrom(const MapFieldBase& other) {
-  SyncMapWithRepeatedField();
+void TypeDefinedMapFieldBase<Key, T>::MergeFromImpl(MapFieldBase& base,
+                                                    const MapFieldBase& other) {
+  auto& self = static_cast<TypeDefinedMapFieldBase&>(base);
+  self.SyncMapWithRepeatedField();
   const auto& other_field = static_cast<const TypeDefinedMapFieldBase&>(other);
   other_field.SyncMapWithRepeatedField();
-  internal::MapMergeFrom(map_, other_field.map_);
-  SetMapDirty();
+  internal::MapMergeFrom(self.map_, other_field.map_);
+  self.SetMapDirty();
 }
 
 template <typename Key, typename T>
-size_t TypeDefinedMapFieldBase<Key, T>::SpaceUsedExcludingSelfNoLock() const {
+size_t TypeDefinedMapFieldBase<Key, T>::SpaceUsedExcludingSelfNoLockImpl(
+    const MapFieldBase& map) {
+  auto& self = static_cast<const TypeDefinedMapFieldBase&>(map);
   size_t size = 0;
-  if (auto* p = maybe_payload()) {
+  if (auto* p = self.maybe_payload()) {
     size += p->repeated_field.SpaceUsedExcludingSelfLong();
   }
   // We can't compile this expression for DynamicMapField even though it is
@@ -152,14 +158,15 @@ size_t TypeDefinedMapFieldBase<Key, T>::SpaceUsedExcludingSelfNoLock() const {
   std::get<std::is_same<Map<Key, T>, Map<MapKey, MapValueRef>>::value>(
       std::make_tuple(
           [&](const auto& map) { size += map.SpaceUsedExcludingSelfLong(); },
-          [](const auto&) {}))(map_);
-
+          [](const auto&) {}))(self.map_);
   return size;
 }
 
 template <typename Key, typename T>
-void TypeDefinedMapFieldBase<Key, T>::UnsafeShallowSwap(MapFieldBase* other) {
-  InternalSwap(DownCast<TypeDefinedMapFieldBase*>(other));
+void TypeDefinedMapFieldBase<Key, T>::UnsafeShallowSwapImpl(MapFieldBase& lhs,
+                                                            MapFieldBase& rhs) {
+  static_cast<TypeDefinedMapFieldBase&>(lhs).InternalSwap(
+      static_cast<TypeDefinedMapFieldBase*>(&rhs));
 }
 
 template <typename Key, typename T>
@@ -174,8 +181,9 @@ void TypeDefinedMapFieldBase<Key, T>::InternalSwap(
 template <typename Derived, typename Key, typename T,
           WireFormatLite::FieldType kKeyFieldType,
           WireFormatLite::FieldType kValueFieldType>
-const Message* MapField<Derived, Key, T, kKeyFieldType,
-                        kValueFieldType>::GetPrototype() const {
+const Message*
+MapField<Derived, Key, T, kKeyFieldType, kValueFieldType>::GetPrototypeImpl(
+    const MapFieldBase&) {
   return Derived::internal_default_instance();
 }
 
diff --git a/src/google/protobuf/map_field_test.cc b/src/google/protobuf/map_field_test.cc
index eed1f216bf..8ef859eff5 100644
--- a/src/google/protobuf/map_field_test.cc
+++ b/src/google/protobuf/map_field_test.cc
@@ -5,6 +5,7 @@
 // license that can be found in the LICENSE file or at
 // https://developers.google.com/open-source/licenses/bsd
 
+#include <cstdint>
 #include <memory>
 
 #include <gmock/gmock.h>
@@ -24,6 +25,7 @@
 #include "google/protobuf/message.h"
 #include "google/protobuf/repeated_field.h"
 #include "google/protobuf/unittest.pb.h"
+#include "google/protobuf/wire_format_lite.h"
 
 // Must be included last.
 #include "google/protobuf/port_def.inc"
@@ -35,28 +37,19 @@ namespace internal {
 
 using unittest::TestAllTypes;
 
-class MapFieldBaseStub : public TypeDefinedMapFieldBase<int32_t, int32_t> {
- public:
-  using InternalArenaConstructable_ = void;
-  typedef void DestructorSkippable_;
-  MapFieldBaseStub() {}
-  virtual ~MapFieldBaseStub() {}
-  explicit MapFieldBaseStub(Arena* arena)
-      : MapFieldBaseStub::TypeDefinedMapFieldBase(arena) {}
-
-  const Message* GetPrototype() const override {
-    return unittest::TestMap_MapInt32Int32Entry_DoNotUse::
-        internal_default_instance();
+struct MapFieldTestPeer {
+  static auto GetArena(const RepeatedPtrFieldBase& v) { return v.GetArena(); }
+  template <typename T>
+  static auto& GetMap(T& t) {
+    return t.map_;
   }
-
-  Arena* GetArenaForInternalRepeatedField() {
-    auto* repeated_field = MutableRepeatedField();
-    return repeated_field->GetArena();
-  }
-
-  using MapFieldBaseStub::TypeDefinedMapFieldBase::map_;
 };
 
+using TestMapField = ::google::protobuf::internal::MapField<
+    unittest::TestMap_MapInt32Int32Entry_DoNotUse, ::int32_t, ::int32_t,
+    ::google::protobuf::internal::WireFormatLite::TYPE_INT32,
+    ::google::protobuf::internal::WireFormatLite::TYPE_INT32>;
+
 class MapFieldBasePrimitiveTest : public testing::TestWithParam<bool> {
  protected:
   typedef unittest::TestMap_MapInt32Int32Entry_DoNotUse EntryType;
@@ -156,20 +149,20 @@ TEST_P(MapFieldBasePrimitiveTest, Arena) {
     // repeated fields are allocated from arenas.
     // NoHeapChecker no_heap;
 
-    MapFieldBaseStub* map_field =
-        Arena::CreateMessage<MapFieldBaseStub>(&arena);
+    TestMapField* map_field = Arena::CreateMessage<TestMapField>(&arena);
 
     // Trigger conversion to repeated field.
     EXPECT_TRUE(map_field->MutableRepeatedField() != nullptr);
 
-    EXPECT_EQ(map_field->GetArenaForInternalRepeatedField(), &arena);
+    EXPECT_EQ(MapFieldTestPeer::GetArena(map_field->GetRepeatedField()),
+              &arena);
   }
 }
 
 TEST_P(MapFieldBasePrimitiveTest, EnforceNoArena) {
-  std::unique_ptr<MapFieldBaseStub> map_field(
-      Arena::CreateMessage<MapFieldBaseStub>(nullptr));
-  EXPECT_EQ(map_field->GetArenaForInternalRepeatedField(), nullptr);
+  std::unique_ptr<TestMapField> map_field(
+      Arena::CreateMessage<TestMapField>(nullptr));
+  EXPECT_EQ(MapFieldTestPeer::GetArena(map_field->GetRepeatedField()), nullptr);
 }
 
 namespace {
diff --git a/src/google/protobuf/repeated_ptr_field.h b/src/google/protobuf/repeated_ptr_field.h
index fcc3186b6c..51c8065685 100644
--- a/src/google/protobuf/repeated_ptr_field.h
+++ b/src/google/protobuf/repeated_ptr_field.h
@@ -662,7 +662,7 @@ class PROTOBUF_EXPORT RepeatedPtrFieldBase {
   // reinterpreting pointers as being to Message instead of a specific Message
   // subclass.
   friend class MapFieldBase;
-  friend class MapFieldBaseStub;
+  friend struct MapFieldTestPeer;
 
   // The table-driven MergePartialFromCodedStream implementation needs to
   // operate on RepeatedPtrField<MessageLite>.