Move Transaction logs to arena allocator.

And change some containers to reduce memory usage.

Also update `SafeMap` with some C++17 functions, namely all
`erase()` and `extract()` overloads and `insert()` overloads
taking a "node". The `extract()` and `insert()` overloads
are convenient to avoid allocation/dealocation when updating
the key of an entry.

Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing
Bug: 181943478
Change-Id: I791d00598ac288b1f31c92daa63b5b3dcf6b41fa
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 7a9c7fa..5a49f50 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -2416,8 +2416,8 @@
 }
 
 void Runtime::VisitTransactionRoots(RootVisitor* visitor) {
-  for (auto& transaction : preinitialization_transactions_) {
-    transaction->VisitRoots(visitor);
+  for (Transaction& transaction : preinitialization_transactions_) {
+    transaction.VisitRoots(visitor);
   }
 }
 
@@ -2668,26 +2668,33 @@
 
 void Runtime::EnterTransactionMode(bool strict, mirror::Class* root) {
   DCHECK(IsAotCompiler());
+  ArenaPool* arena_pool = nullptr;
+  ArenaStack* arena_stack = nullptr;
   if (preinitialization_transactions_.empty()) {  // Top-level transaction?
     // Make initialized classes visibly initialized now. If that happened during the transaction
     // and then the transaction was aborted, we would roll back the status update but not the
     // ClassLinker's bookkeeping structures, so these classes would never be visibly initialized.
     GetClassLinker()->MakeInitializedClassesVisiblyInitialized(Thread::Current(), /*wait=*/ true);
+    // Pass the runtime `ArenaPool` to the transaction.
+    arena_pool = GetArenaPool();
+  } else {
+    // Pass the `ArenaStack` from previous transaction to the new one.
+    arena_stack = preinitialization_transactions_.front().GetArenaStack();
   }
-  preinitialization_transactions_.push_back(std::make_unique<Transaction>(strict, root));
+  preinitialization_transactions_.emplace_front(strict, root, arena_stack, arena_pool);
 }
 
 void Runtime::ExitTransactionMode() {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  preinitialization_transactions_.pop_back();
+  preinitialization_transactions_.pop_front();
 }
 
 void Runtime::RollbackAndExitTransactionMode() {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  preinitialization_transactions_.back()->Rollback();
-  preinitialization_transactions_.pop_back();
+  preinitialization_transactions_.front().Rollback();
+  preinitialization_transactions_.pop_front();
 }
 
 bool Runtime::IsTransactionAborted() const {
@@ -2711,9 +2718,14 @@
   return IsActiveTransaction() && GetTransaction()->IsStrict();
 }
 
-const std::unique_ptr<Transaction>& Runtime::GetTransaction() const {
+const Transaction* Runtime::GetTransaction() const {
   DCHECK(!preinitialization_transactions_.empty());
-  return preinitialization_transactions_.back();
+  return &preinitialization_transactions_.front();
+}
+
+Transaction* Runtime::GetTransaction() {
+  DCHECK(!preinitialization_transactions_.empty());
+  return &preinitialization_transactions_.front();
 }
 
 void Runtime::AbortTransactionAndThrowAbortError(Thread* self, const std::string& abort_message) {
@@ -2735,43 +2747,55 @@
   GetTransaction()->ThrowAbortError(self, nullptr);
 }
 
-void Runtime::RecordWriteFieldBoolean(mirror::Object* obj, MemberOffset field_offset,
-                                      uint8_t value, bool is_volatile) const {
+void Runtime::RecordWriteFieldBoolean(mirror::Object* obj,
+                                      MemberOffset field_offset,
+                                      uint8_t value,
+                                      bool is_volatile) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
   GetTransaction()->RecordWriteFieldBoolean(obj, field_offset, value, is_volatile);
 }
 
-void Runtime::RecordWriteFieldByte(mirror::Object* obj, MemberOffset field_offset,
-                                   int8_t value, bool is_volatile) const {
+void Runtime::RecordWriteFieldByte(mirror::Object* obj,
+                                   MemberOffset field_offset,
+                                   int8_t value,
+                                   bool is_volatile) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
   GetTransaction()->RecordWriteFieldByte(obj, field_offset, value, is_volatile);
 }
 
-void Runtime::RecordWriteFieldChar(mirror::Object* obj, MemberOffset field_offset,
-                                   uint16_t value, bool is_volatile) const {
+void Runtime::RecordWriteFieldChar(mirror::Object* obj,
+                                   MemberOffset field_offset,
+                                   uint16_t value,
+                                   bool is_volatile) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
   GetTransaction()->RecordWriteFieldChar(obj, field_offset, value, is_volatile);
 }
 
-void Runtime::RecordWriteFieldShort(mirror::Object* obj, MemberOffset field_offset,
-                                    int16_t value, bool is_volatile) const {
+void Runtime::RecordWriteFieldShort(mirror::Object* obj,
+                                    MemberOffset field_offset,
+                                    int16_t value,
+                                    bool is_volatile) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
   GetTransaction()->RecordWriteFieldShort(obj, field_offset, value, is_volatile);
 }
 
-void Runtime::RecordWriteField32(mirror::Object* obj, MemberOffset field_offset,
-                                 uint32_t value, bool is_volatile) const {
+void Runtime::RecordWriteField32(mirror::Object* obj,
+                                 MemberOffset field_offset,
+                                 uint32_t value,
+                                 bool is_volatile) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
   GetTransaction()->RecordWriteField32(obj, field_offset, value, is_volatile);
 }
 
-void Runtime::RecordWriteField64(mirror::Object* obj, MemberOffset field_offset,
-                                 uint64_t value, bool is_volatile) const {
+void Runtime::RecordWriteField64(mirror::Object* obj,
+                                 MemberOffset field_offset,
+                                 uint64_t value,
+                                 bool is_volatile) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
   GetTransaction()->RecordWriteField64(obj, field_offset, value, is_volatile);
@@ -2780,54 +2804,51 @@
 void Runtime::RecordWriteFieldReference(mirror::Object* obj,
                                         MemberOffset field_offset,
                                         ObjPtr<mirror::Object> value,
-                                        bool is_volatile) const {
+                                        bool is_volatile) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  GetTransaction()->RecordWriteFieldReference(obj,
-                                                            field_offset,
-                                                            value.Ptr(),
-                                                            is_volatile);
+  GetTransaction()->RecordWriteFieldReference(obj, field_offset, value.Ptr(), is_volatile);
 }
 
-void Runtime::RecordWriteArray(mirror::Array* array, size_t index, uint64_t value) const {
+void Runtime::RecordWriteArray(mirror::Array* array, size_t index, uint64_t value) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
   GetTransaction()->RecordWriteArray(array, index, value);
 }
 
-void Runtime::RecordStrongStringInsertion(ObjPtr<mirror::String> s) const {
+void Runtime::RecordStrongStringInsertion(ObjPtr<mirror::String> s) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
   GetTransaction()->RecordStrongStringInsertion(s);
 }
 
-void Runtime::RecordWeakStringInsertion(ObjPtr<mirror::String> s) const {
+void Runtime::RecordWeakStringInsertion(ObjPtr<mirror::String> s) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
   GetTransaction()->RecordWeakStringInsertion(s);
 }
 
-void Runtime::RecordStrongStringRemoval(ObjPtr<mirror::String> s) const {
+void Runtime::RecordStrongStringRemoval(ObjPtr<mirror::String> s) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
   GetTransaction()->RecordStrongStringRemoval(s);
 }
 
-void Runtime::RecordWeakStringRemoval(ObjPtr<mirror::String> s) const {
+void Runtime::RecordWeakStringRemoval(ObjPtr<mirror::String> s) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
   GetTransaction()->RecordWeakStringRemoval(s);
 }
 
 void Runtime::RecordResolveString(ObjPtr<mirror::DexCache> dex_cache,
-                                  dex::StringIndex string_idx) const {
+                                  dex::StringIndex string_idx) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
   GetTransaction()->RecordResolveString(dex_cache, string_idx);
 }
 
 void Runtime::RecordResolveMethodType(ObjPtr<mirror::DexCache> dex_cache,
-                                      dex::ProtoIndex proto_idx) const {
+                                      dex::ProtoIndex proto_idx) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
   GetTransaction()->RecordResolveMethodType(dex_cache, proto_idx);
diff --git a/runtime/runtime.h b/runtime/runtime.h
index ff7eb9b..080ba63 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -20,6 +20,7 @@
 #include <jni.h>
 #include <stdio.h>
 
+#include <forward_list>
 #include <iosfwd>
 #include <memory>
 #include <set>
@@ -550,7 +551,8 @@
   // do them in one function.
   void RollbackAndExitTransactionMode() REQUIRES_SHARED(Locks::mutator_lock_);
   bool IsTransactionAborted() const;
-  const std::unique_ptr<Transaction>& GetTransaction() const;
+  const Transaction* GetTransaction() const;
+  Transaction* GetTransaction();
   bool IsActiveStrictTransactionMode() const;
 
   void AbortTransactionAndThrowAbortError(Thread* self, const std::string& abort_message)
@@ -558,36 +560,48 @@
   void ThrowTransactionAbortError(Thread* self)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  void RecordWriteFieldBoolean(mirror::Object* obj, MemberOffset field_offset, uint8_t value,
-                               bool is_volatile) const;
-  void RecordWriteFieldByte(mirror::Object* obj, MemberOffset field_offset, int8_t value,
-                            bool is_volatile) const;
-  void RecordWriteFieldChar(mirror::Object* obj, MemberOffset field_offset, uint16_t value,
-                            bool is_volatile) const;
-  void RecordWriteFieldShort(mirror::Object* obj, MemberOffset field_offset, int16_t value,
-                          bool is_volatile) const;
-  void RecordWriteField32(mirror::Object* obj, MemberOffset field_offset, uint32_t value,
-                          bool is_volatile) const;
-  void RecordWriteField64(mirror::Object* obj, MemberOffset field_offset, uint64_t value,
-                          bool is_volatile) const;
+  void RecordWriteFieldBoolean(mirror::Object* obj,
+                               MemberOffset field_offset,
+                               uint8_t value,
+                               bool is_volatile);
+  void RecordWriteFieldByte(mirror::Object* obj,
+                            MemberOffset field_offset,
+                            int8_t value,
+                            bool is_volatile);
+  void RecordWriteFieldChar(mirror::Object* obj,
+                            MemberOffset field_offset,
+                            uint16_t value,
+                            bool is_volatile);
+  void RecordWriteFieldShort(mirror::Object* obj,
+                             MemberOffset field_offset,
+                             int16_t value,
+                             bool is_volatile);
+  void RecordWriteField32(mirror::Object* obj,
+                          MemberOffset field_offset,
+                          uint32_t value,
+                          bool is_volatile);
+  void RecordWriteField64(mirror::Object* obj,
+                          MemberOffset field_offset,
+                          uint64_t value,
+                          bool is_volatile);
   void RecordWriteFieldReference(mirror::Object* obj,
                                  MemberOffset field_offset,
                                  ObjPtr<mirror::Object> value,
-                                 bool is_volatile) const
+                                 bool is_volatile)
       REQUIRES_SHARED(Locks::mutator_lock_);
-  void RecordWriteArray(mirror::Array* array, size_t index, uint64_t value) const
+  void RecordWriteArray(mirror::Array* array, size_t index, uint64_t value)
       REQUIRES_SHARED(Locks::mutator_lock_);
-  void RecordStrongStringInsertion(ObjPtr<mirror::String> s) const
+  void RecordStrongStringInsertion(ObjPtr<mirror::String> s)
       REQUIRES(Locks::intern_table_lock_);
-  void RecordWeakStringInsertion(ObjPtr<mirror::String> s) const
+  void RecordWeakStringInsertion(ObjPtr<mirror::String> s)
       REQUIRES(Locks::intern_table_lock_);
-  void RecordStrongStringRemoval(ObjPtr<mirror::String> s) const
+  void RecordStrongStringRemoval(ObjPtr<mirror::String> s)
       REQUIRES(Locks::intern_table_lock_);
-  void RecordWeakStringRemoval(ObjPtr<mirror::String> s) const
+  void RecordWeakStringRemoval(ObjPtr<mirror::String> s)
       REQUIRES(Locks::intern_table_lock_);
-  void RecordResolveString(ObjPtr<mirror::DexCache> dex_cache, dex::StringIndex string_idx) const
+  void RecordResolveString(ObjPtr<mirror::DexCache> dex_cache, dex::StringIndex string_idx)
       REQUIRES_SHARED(Locks::mutator_lock_);
-  void RecordResolveMethodType(ObjPtr<mirror::DexCache> dex_cache, dex::ProtoIndex proto_idx) const
+  void RecordResolveMethodType(ObjPtr<mirror::DexCache> dex_cache, dex::ProtoIndex proto_idx)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   void SetFaultMessage(const std::string& message);
@@ -710,12 +724,12 @@
   ArenaPool* GetArenaPool() {
     return arena_pool_.get();
   }
-  ArenaPool* GetJitArenaPool() {
-    return jit_arena_pool_.get();
-  }
   const ArenaPool* GetArenaPool() const {
     return arena_pool_.get();
   }
+  ArenaPool* GetJitArenaPool() {
+    return jit_arena_pool_.get();
+  }
 
   void ReclaimArenaPoolMemory();
 
@@ -1237,7 +1251,7 @@
   // Support nested transactions, maintain a list containing all transactions. Transactions are
   // handled under a stack discipline. Because GC needs to go over all transactions, we choose list
   // as substantial data structure instead of stack.
-  std::list<std::unique_ptr<Transaction>> preinitialization_transactions_;
+  std::forward_list<Transaction> preinitialization_transactions_;
 
   // If kNone, verification is disabled. kEnable by default.
   verifier::VerifyMode verify_;
diff --git a/runtime/transaction.cc b/runtime/transaction.cc
index 0aec48c..193c111 100644
--- a/runtime/transaction.cc
+++ b/runtime/transaction.cc
@@ -40,14 +40,25 @@
 // TODO: remove (only used for debugging purpose).
 static constexpr bool kEnableTransactionStats = false;
 
-Transaction::Transaction(bool strict, mirror::Class* root)
-    : aborted_(false),
+Transaction::Transaction(bool strict,
+                         mirror::Class* root,
+                         ArenaStack* arena_stack,
+                         ArenaPool* arena_pool)
+    : arena_stack_(std::nullopt),
+      allocator_(arena_stack != nullptr ? arena_stack : &arena_stack_.emplace(arena_pool)),
+      object_logs_(std::less<mirror::Object*>(), allocator_.Adapter(kArenaAllocTransaction)),
+      array_logs_(std::less<mirror::Array*>(), allocator_.Adapter(kArenaAllocTransaction)),
+      intern_string_logs_(allocator_.Adapter(kArenaAllocTransaction)),
+      resolve_string_logs_(allocator_.Adapter(kArenaAllocTransaction)),
+      resolve_method_type_logs_(allocator_.Adapter(kArenaAllocTransaction)),
+      aborted_(false),
       rolling_back_(false),
       heap_(Runtime::Current()->GetHeap()),
       strict_(strict),
       root_(root),
       assert_no_new_records_reason_(nullptr) {
   DCHECK(Runtime::Current()->IsAotCompiler());
+  DCHECK_NE(arena_stack != nullptr, arena_pool != nullptr);
 }
 
 Transaction::~Transaction() {
@@ -62,15 +73,20 @@
     for (const auto& it : array_logs_) {
       array_values_count += it.second.Size();
     }
-    size_t intern_string_count = intern_string_logs_.size();
-    size_t resolve_string_count = resolve_string_logs_.size();
+    size_t intern_string_count =
+        std::distance(intern_string_logs_.begin(), intern_string_logs_.end());
+    size_t resolve_string_count =
+        std::distance(resolve_string_logs_.begin(), resolve_string_logs_.end());
+    size_t resolve_method_type_count =
+        std::distance(resolve_method_type_logs_.begin(), resolve_method_type_logs_.end());
     LOG(INFO) << "Transaction::~Transaction"
               << ": objects_count=" << objects_count
               << ", field_values_count=" << field_values_count
               << ", array_count=" << array_count
               << ", array_values_count=" << array_values_count
               << ", intern_string_count=" << intern_string_count
-              << ", resolve_string_count=" << resolve_string_count;
+              << ", resolve_string_count=" << resolve_string_count
+              << ", resolve_method_type_count=" << resolve_method_type_count;
   }
 }
 
@@ -150,13 +166,17 @@
   }
 }
 
+inline Transaction::ObjectLog& Transaction::GetOrCreateObjectLog(mirror::Object* obj) {
+  return object_logs_.GetOrCreate(obj, [&]() { return ObjectLog(&allocator_); });
+}
+
 void Transaction::RecordWriteFieldBoolean(mirror::Object* obj,
                                           MemberOffset field_offset,
                                           uint8_t value,
                                           bool is_volatile) {
   DCHECK(obj != nullptr);
   DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
-  ObjectLog& object_log = object_logs_[obj];
+  ObjectLog& object_log = GetOrCreateObjectLog(obj);
   object_log.LogBooleanValue(field_offset, value, is_volatile);
 }
 
@@ -166,7 +186,7 @@
                                        bool is_volatile) {
   DCHECK(obj != nullptr);
   DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
-  ObjectLog& object_log = object_logs_[obj];
+  ObjectLog& object_log = GetOrCreateObjectLog(obj);
   object_log.LogByteValue(field_offset, value, is_volatile);
 }
 
@@ -176,7 +196,7 @@
                                        bool is_volatile) {
   DCHECK(obj != nullptr);
   DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
-  ObjectLog& object_log = object_logs_[obj];
+  ObjectLog& object_log = GetOrCreateObjectLog(obj);
   object_log.LogCharValue(field_offset, value, is_volatile);
 }
 
@@ -187,7 +207,7 @@
                                         bool is_volatile) {
   DCHECK(obj != nullptr);
   DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
-  ObjectLog& object_log = object_logs_[obj];
+  ObjectLog& object_log = GetOrCreateObjectLog(obj);
   object_log.LogShortValue(field_offset, value, is_volatile);
 }
 
@@ -198,7 +218,7 @@
                                      bool is_volatile) {
   DCHECK(obj != nullptr);
   DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
-  ObjectLog& object_log = object_logs_[obj];
+  ObjectLog& object_log = GetOrCreateObjectLog(obj);
   object_log.Log32BitsValue(field_offset, value, is_volatile);
 }
 
@@ -208,7 +228,7 @@
                                      bool is_volatile) {
   DCHECK(obj != nullptr);
   DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
-  ObjectLog& object_log = object_logs_[obj];
+  ObjectLog& object_log = GetOrCreateObjectLog(obj);
   object_log.Log64BitsValue(field_offset, value, is_volatile);
 }
 
@@ -218,7 +238,7 @@
                                             bool is_volatile) {
   DCHECK(obj != nullptr);
   DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
-  ObjectLog& object_log = object_logs_[obj];
+  ObjectLog& object_log = GetOrCreateObjectLog(obj);
   object_log.LogReferenceValue(field_offset, value, is_volatile);
 }
 
@@ -227,12 +247,8 @@
   DCHECK(array->IsArrayInstance());
   DCHECK(!array->IsObjectArray());
   DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
-  auto it = array_logs_.find(array);
-  if (it == array_logs_.end()) {
-    ArrayLog log;
-    it = array_logs_.emplace(array, std::move(log)).first;
-  }
-  it->second.LogValue(index, value);
+  ArrayLog& array_log = array_logs_.GetOrCreate(array, [&]() { return ArrayLog(&allocator_); });
+  array_log.LogValue(index, value);
 }
 
 void Transaction::RecordResolveString(ObjPtr<mirror::DexCache> dex_cache,
@@ -240,7 +256,7 @@
   DCHECK(dex_cache != nullptr);
   DCHECK_LT(string_idx.index_, dex_cache->GetDexFile()->NumStringIds());
   DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
-  resolve_string_logs_.emplace_back(dex_cache, string_idx);
+  resolve_string_logs_.emplace_front(dex_cache, string_idx);
 }
 
 void Transaction::RecordResolveMethodType(ObjPtr<mirror::DexCache> dex_cache,
@@ -248,7 +264,7 @@
   DCHECK(dex_cache != nullptr);
   DCHECK_LT(proto_idx.index_, dex_cache->GetDexFile()->NumProtoIds());
   DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
-  resolve_method_type_logs_.emplace_back(dex_cache, proto_idx);
+  resolve_method_type_logs_.emplace_front(dex_cache, proto_idx);
 }
 
 void Transaction::RecordStrongStringInsertion(ObjPtr<mirror::String> s) {
@@ -334,18 +350,42 @@
 }
 
 void Transaction::VisitRoots(RootVisitor* visitor) {
+  // Transactions are used for single-threaded initialization.
+  // This is the only function that should be called from a different thread,
+  // namely the GC thread, and it is called with the mutator lock held exclusively,
+  // so the data structures in the `Transaction` are protected from concurrent use.
+  DCHECK(Locks::mutator_lock_->IsExclusiveHeld(Thread::Current()));
+
   visitor->VisitRoot(reinterpret_cast<mirror::Object**>(&root_), RootInfo(kRootUnknown));
-  VisitObjectLogs(visitor);
-  VisitArrayLogs(visitor);
+  {
+    // Create a separate `ArenaStack` for this thread.
+    ArenaStack arena_stack(Runtime::Current()->GetArenaPool());
+    VisitObjectLogs(visitor, &arena_stack);
+    VisitArrayLogs(visitor, &arena_stack);
+  }
   VisitInternStringLogs(visitor);
   VisitResolveStringLogs(visitor);
   VisitResolveMethodTypeLogs(visitor);
 }
 
-void Transaction::VisitObjectLogs(RootVisitor* visitor) {
+template <typename MovingRoots, typename Container>
+void UpdateKeys(const MovingRoots& moving_roots, Container& container) {
+  for (const auto& pair : moving_roots) {
+    auto* old_root = pair.first;
+    auto* new_root = pair.second;
+    auto node = container.extract(old_root);
+    CHECK(!node.empty());
+    node.key() = new_root;
+    bool inserted = container.insert(std::move(node)).inserted;
+    CHECK(inserted);
+  }
+}
+
+void Transaction::VisitObjectLogs(RootVisitor* visitor, ArenaStack* arena_stack) {
   // List of moving roots.
+  ScopedArenaAllocator allocator(arena_stack);
   using ObjectPair = std::pair<mirror::Object*, mirror::Object*>;
-  std::list<ObjectPair> moving_roots;
+  ScopedArenaForwardList<ObjectPair> moving_roots(allocator.Adapter(kArenaAllocTransaction));
 
   // Visit roots.
   for (auto& it : object_logs_) {
@@ -354,26 +394,19 @@
     mirror::Object* new_root = old_root;
     visitor->VisitRoot(&new_root, RootInfo(kRootUnknown));
     if (new_root != old_root) {
-      moving_roots.push_back(std::make_pair(old_root, new_root));
+      moving_roots.push_front(std::make_pair(old_root, new_root));
     }
   }
 
   // Update object logs with moving roots.
-  for (const ObjectPair& pair : moving_roots) {
-    mirror::Object* old_root = pair.first;
-    mirror::Object* new_root = pair.second;
-    auto old_root_it = object_logs_.find(old_root);
-    CHECK(old_root_it != object_logs_.end());
-    CHECK(object_logs_.find(new_root) == object_logs_.end());
-    object_logs_.emplace(new_root, std::move(old_root_it->second));
-    object_logs_.erase(old_root_it);
-  }
+  UpdateKeys(moving_roots, object_logs_);
 }
 
-void Transaction::VisitArrayLogs(RootVisitor* visitor) {
+void Transaction::VisitArrayLogs(RootVisitor* visitor, ArenaStack* arena_stack) {
   // List of moving roots.
+  ScopedArenaAllocator allocator(arena_stack);
   using ArrayPair = std::pair<mirror::Array*, mirror::Array*>;
-  std::list<ArrayPair> moving_roots;
+  ScopedArenaForwardList<ArrayPair> moving_roots(allocator.Adapter(kArenaAllocTransaction));
 
   for (auto& it : array_logs_) {
     mirror::Array* old_root = it.first;
@@ -381,20 +414,12 @@
     mirror::Array* new_root = old_root;
     visitor->VisitRoot(reinterpret_cast<mirror::Object**>(&new_root), RootInfo(kRootUnknown));
     if (new_root != old_root) {
-      moving_roots.push_back(std::make_pair(old_root, new_root));
+      moving_roots.push_front(std::make_pair(old_root, new_root));
     }
   }
 
   // Update array logs with moving roots.
-  for (const ArrayPair& pair : moving_roots) {
-    mirror::Array* old_root = pair.first;
-    mirror::Array* new_root = pair.second;
-    auto old_root_it = array_logs_.find(old_root);
-    CHECK(old_root_it != array_logs_.end());
-    CHECK(array_logs_.find(new_root) == array_logs_.end());
-    array_logs_.emplace(new_root, std::move(old_root_it->second));
-    array_logs_.erase(old_root_it);
-  }
+  UpdateKeys(moving_roots, array_logs_);
 }
 
 void Transaction::VisitInternStringLogs(RootVisitor* visitor) {
@@ -657,10 +682,8 @@
 }
 
 void Transaction::ArrayLog::LogValue(size_t index, uint64_t value) {
-  auto it = array_values_.find(index);
-  if (it == array_values_.end()) {
-    array_values_.insert(std::make_pair(index, value));
-  }
+  // Add a mapping if there is none yet.
+  array_values_.FindOrAdd(index, value);
 }
 
 void Transaction::ArrayLog::Undo(mirror::Array* array) const {
@@ -724,7 +747,7 @@
 Transaction* ScopedAssertNoNewTransactionRecords::InstallAssertion(const char* reason) {
   Transaction* transaction = nullptr;
   if (kIsDebugBuild && Runtime::Current()->IsActiveTransaction()) {
-    transaction = Runtime::Current()->GetTransaction().get();
+    transaction = Runtime::Current()->GetTransaction();
     if (transaction != nullptr) {
       CHECK(transaction->assert_no_new_records_reason_ == nullptr)
           << "old: " << transaction->assert_no_new_records_reason_ << " new: " << reason;
@@ -736,7 +759,7 @@
 
 void ScopedAssertNoNewTransactionRecords::RemoveAssertion(Transaction* transaction) {
   if (kIsDebugBuild) {
-    CHECK(Runtime::Current()->GetTransaction().get() == transaction);
+    CHECK(Runtime::Current()->GetTransaction() == transaction);
     CHECK(transaction->assert_no_new_records_reason_ != nullptr);
     transaction->assert_no_new_records_reason_ = nullptr;
   }
diff --git a/runtime/transaction.h b/runtime/transaction.h
index 8ee922f..8cd18e3 100644
--- a/runtime/transaction.h
+++ b/runtime/transaction.h
@@ -17,6 +17,7 @@
 #ifndef ART_RUNTIME_TRANSACTION_H_
 #define ART_RUNTIME_TRANSACTION_H_
 
+#include "base/scoped_arena_containers.h"
 #include "base/macros.h"
 #include "base/mutex.h"
 #include "base/safe_map.h"
@@ -47,9 +48,13 @@
  public:
   static constexpr const char* kAbortExceptionDescriptor = "Ldalvik/system/TransactionAbortError;";
 
-  Transaction(bool strict, mirror::Class* root);
+  Transaction(bool strict, mirror::Class* root, ArenaStack* arena_stack, ArenaPool* arena_pool);
   ~Transaction();
 
+  ArenaStack* GetArenaStack() {
+    return allocator_.GetArenaStack();
+  }
+
   void Abort(const std::string& abort_message)
       REQUIRES_SHARED(Locks::mutator_lock_);
   void ThrowAbortError(Thread* self, const std::string* abort_message)
@@ -157,7 +162,8 @@
       return field_values_.size();
     }
 
-    ObjectLog() = default;
+    explicit ObjectLog(ScopedArenaAllocator* allocator)
+        : field_values_(std::less<uint32_t>(), allocator->Adapter(kArenaAllocTransaction)) {}
     ObjectLog(ObjectLog&& log) = default;
 
    private:
@@ -189,7 +195,7 @@
                         const FieldValue& field_value) const REQUIRES_SHARED(Locks::mutator_lock_);
 
     // Maps field's offset to its value.
-    std::map<uint32_t, FieldValue> field_values_;
+    ScopedArenaSafeMap<uint32_t, FieldValue> field_values_;
 
     DISALLOW_COPY_AND_ASSIGN(ObjectLog);
   };
@@ -204,7 +210,9 @@
       return array_values_.size();
     }
 
-    ArrayLog() = default;
+    explicit ArrayLog(ScopedArenaAllocator* allocator)
+        : array_values_(std::less<size_t>(), allocator->Adapter(kArenaAllocTransaction)) {}
+
     ArrayLog(ArrayLog&& log) = default;
 
    private:
@@ -215,7 +223,7 @@
 
     // Maps index to value.
     // TODO use JValue instead ?
-    std::map<size_t, uint64_t> array_values_;
+    ScopedArenaSafeMap<size_t, uint64_t> array_values_;
 
     DISALLOW_COPY_AND_ASSIGN(ArrayLog);
   };
@@ -293,9 +301,9 @@
   void UndoResolveMethodTypeModifications()
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  void VisitObjectLogs(RootVisitor* visitor)
+  void VisitObjectLogs(RootVisitor* visitor, ArenaStack* arena_stack)
       REQUIRES_SHARED(Locks::mutator_lock_);
-  void VisitArrayLogs(RootVisitor* visitor)
+  void VisitArrayLogs(RootVisitor* visitor, ArenaStack* arena_stack)
       REQUIRES_SHARED(Locks::mutator_lock_);
   void VisitInternStringLogs(RootVisitor* visitor)
       REQUIRES_SHARED(Locks::mutator_lock_);
@@ -306,11 +314,19 @@
 
   const std::string& GetAbortMessage() const;
 
-  std::map<mirror::Object*, ObjectLog> object_logs_;
-  std::map<mirror::Array*, ArrayLog> array_logs_ ;
-  std::list<InternStringLog> intern_string_logs_;
-  std::list<ResolveStringLog> resolve_string_logs_;
-  std::list<ResolveMethodTypeLog> resolve_method_type_logs_;
+  ObjectLog& GetOrCreateObjectLog(mirror::Object* obj);
+
+  // The top-level transaction creates an `ArenaStack` which is then
+  // passed down to nested transactions.
+  std::optional<ArenaStack> arena_stack_;
+  // The allocator uses the `ArenaStack` from the top-level transaction.
+  ScopedArenaAllocator allocator_;
+
+  ScopedArenaSafeMap<mirror::Object*, ObjectLog> object_logs_;
+  ScopedArenaSafeMap<mirror::Array*, ArrayLog> array_logs_ ;
+  ScopedArenaForwardList<InternStringLog> intern_string_logs_;
+  ScopedArenaForwardList<ResolveStringLog> resolve_string_logs_;
+  ScopedArenaForwardList<ResolveMethodTypeLog> resolve_method_type_logs_;
   bool aborted_;
   bool rolling_back_;  // Single thread, no race.
   gc::Heap* const heap_;
diff --git a/runtime/transaction_test.cc b/runtime/transaction_test.cc
index 5c78505..88e3f4f 100644
--- a/runtime/transaction_test.cc
+++ b/runtime/transaction_test.cc
@@ -730,7 +730,9 @@
   ASSERT_TRUE(heap->ObjectIsInBootImageSpace(array_iftable.Get()));
 
   // Test non-strict transaction.
-  Transaction transaction(/*strict=*/ false, /*root=*/ nullptr);
+  ArenaPool* arena_pool = Runtime::Current()->GetArenaPool();
+  Transaction transaction(
+      /*strict=*/ false, /*root=*/ nullptr, /*arena_stack=*/ nullptr, arena_pool);
   // Static field in boot image.
   EXPECT_TRUE(transaction.WriteConstraint(boolean_class.Get()));
   EXPECT_FALSE(transaction.ReadConstraint(boolean_class.Get()));
@@ -754,7 +756,8 @@
   EXPECT_FALSE(transaction.WriteValueConstraint(long_array.Get()));
 
   // Test strict transaction.
-  Transaction strict_transaction(/*strict=*/ true, /*root=*/ static_field_class.Get());
+  Transaction strict_transaction(
+      /*strict=*/ true, /*root=*/ static_field_class.Get(), /*arena_stack=*/ nullptr, arena_pool);
   // Static field in boot image.
   EXPECT_TRUE(strict_transaction.WriteConstraint(boolean_class.Get()));
   EXPECT_TRUE(strict_transaction.ReadConstraint(boolean_class.Get()));