| /* |
| * Copyright 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #ifndef ART_RUNTIME_GC_COLLECTOR_MARK_COMPACT_H_ |
| #define ART_RUNTIME_GC_COLLECTOR_MARK_COMPACT_H_ |
| |
| #include <memory> |
| #include <unordered_set> |
| |
| #include "base/atomic.h" |
| #include "barrier.h" |
| #include "base/macros.h" |
| #include "base/mutex.h" |
| #include "garbage_collector.h" |
| #include "gc/accounting/atomic_stack.h" |
| #include "gc/accounting/bitmap-inl.h" |
| #include "gc/accounting/heap_bitmap.h" |
| #include "gc_root.h" |
| #include "immune_spaces.h" |
| #include "offsets.h" |
| |
| namespace art { |
| namespace gc { |
| |
| class Heap; |
| |
| namespace space { |
| class BumpPointerSpace; |
| } // namespace space |
| |
| namespace collector { |
| class MarkCompact : public GarbageCollector { |
| public: |
| static constexpr size_t kAlignment = kObjectAlignment; |
| |
| explicit MarkCompact(Heap* heap); |
| |
| ~MarkCompact() {} |
| |
| void RunPhases() override REQUIRES(!Locks::mutator_lock_); |
| |
| // Updated before (or in) pre-compaction pause and is accessed only in the |
| // pause or during concurrent compaction. The flag is reset after compaction |
| // is completed and never accessed by mutators. Therefore, safe to update |
| // without any memory ordering. |
| bool IsCompacting() const { |
| return compacting_; |
| } |
| |
| GcType GetGcType() const override { |
| return kGcTypeFull; |
| } |
| |
| CollectorType GetCollectorType() const override { |
| return kCollectorTypeCMC; |
| } |
| |
| Barrier& GetBarrier() { |
| return gc_barrier_; |
| } |
| |
| mirror::Object* MarkObject(mirror::Object* obj) override |
| REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| |
| void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj, |
| bool do_atomic_update) override |
| REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| |
| void VisitRoots(mirror::Object*** roots, |
| size_t count, |
| const RootInfo& info) override |
| REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| void VisitRoots(mirror::CompressedReference<mirror::Object>** roots, |
| size_t count, |
| const RootInfo& info) override |
| REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| |
| bool IsNullOrMarkedHeapReference(mirror::HeapReference<mirror::Object>* obj, |
| bool do_atomic_update) override |
| REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| |
| void RevokeAllThreadLocalBuffers() override; |
| |
| void DelayReferenceReferent(ObjPtr<mirror::Class> klass, |
| ObjPtr<mirror::Reference> reference) override |
| REQUIRES_SHARED(Locks::mutator_lock_, Locks::heap_bitmap_lock_); |
| |
| mirror::Object* IsMarked(mirror::Object* obj) override |
| REQUIRES_SHARED(Locks::mutator_lock_, Locks::heap_bitmap_lock_); |
| |
| // Perform GC-root updation and heap protection so that during the concurrent |
| // compaction phase we can receive faults and compact the corresponding pages |
| // on the fly. This is performed in a STW pause. |
| void CompactionPause() REQUIRES(Locks::mutator_lock_, !Locks::heap_bitmap_lock_); |
| |
| mirror::Object* GetFromSpaceAddrFromBarrier(mirror::Object* old_ref) { |
| CHECK(compacting_); |
| if (live_words_bitmap_->HasAddress(old_ref)) { |
| return GetFromSpaceAddr(old_ref); |
| } |
| return old_ref; |
| } |
| |
| private: |
| using ObjReference = mirror::ObjectReference</*kPoisonReferences*/ false, mirror::Object>; |
| // Number of bits (live-words) covered by a single chunk-info (below) |
| // entry/word. |
| // TODO: Since popcount is performed usomg SIMD instructions, we should |
| // consider using 128-bit in order to halve the chunk-info size. |
| static constexpr uint32_t kBitsPerVectorWord = kBitsPerIntPtrT; |
| static constexpr uint32_t kOffsetChunkSize = kBitsPerVectorWord * kAlignment; |
| static_assert(kOffsetChunkSize < kPageSize); |
| // Bitmap with bits corresponding to every live word set. For an object |
| // which is 4 words in size will have the corresponding 4 bits set. This is |
| // required for efficient computation of new-address (post-compaction) from |
| // the given old-address (pre-compaction). |
| template <size_t kAlignment> |
| class LiveWordsBitmap : private accounting::MemoryRangeBitmap<kAlignment> { |
| using Bitmap = accounting::Bitmap; |
| using MemRangeBitmap = accounting::MemoryRangeBitmap<kAlignment>; |
| |
| public: |
| static_assert(IsPowerOfTwo(kBitsPerVectorWord)); |
| static_assert(IsPowerOfTwo(Bitmap::kBitsPerBitmapWord)); |
| static_assert(kBitsPerVectorWord >= Bitmap::kBitsPerBitmapWord); |
| static constexpr uint32_t kBitmapWordsPerVectorWord = |
| kBitsPerVectorWord / Bitmap::kBitsPerBitmapWord; |
| static_assert(IsPowerOfTwo(kBitmapWordsPerVectorWord)); |
| static LiveWordsBitmap* Create(uintptr_t begin, uintptr_t end); |
| |
| // Return offset (within the indexed chunk-info) of the nth live word. |
| uint32_t FindNthLiveWordOffset(size_t chunk_idx, uint32_t n) const; |
| // Sets all bits in the bitmap corresponding to the given range. Also |
| // returns the bit-index of the first word. |
| ALWAYS_INLINE uintptr_t SetLiveWords(uintptr_t begin, size_t size); |
| // Count number of live words upto the given bit-index. This is to be used |
| // to compute the post-compact address of an old reference. |
| ALWAYS_INLINE size_t CountLiveWordsUpto(size_t bit_idx) const; |
| // Call 'visitor' for every stride of contiguous marked bits in the live-words |
| // bitmap, starting from begin_bit_idx. Only visit 'bytes' live bytes or |
| // until 'end', whichever comes first. |
| // Visitor is called with index of the first marked bit in the stride, |
| // stride size and whether it's the last stride in the given range or not. |
| template <typename Visitor> |
| ALWAYS_INLINE void VisitLiveStrides(uintptr_t begin_bit_idx, |
| uint8_t* end, |
| const size_t bytes, |
| Visitor&& visitor) const; |
| // Count the number of live bytes in the given vector entry. |
| size_t LiveBytesInBitmapWord(size_t chunk_idx) const; |
| void ClearBitmap() { Bitmap::Clear(); } |
| ALWAYS_INLINE uintptr_t Begin() const { return MemRangeBitmap::CoverBegin(); } |
| ALWAYS_INLINE bool HasAddress(mirror::Object* obj) const { |
| return MemRangeBitmap::HasAddress(reinterpret_cast<uintptr_t>(obj)); |
| } |
| ALWAYS_INLINE bool Test(uintptr_t bit_index) const { |
| return Bitmap::TestBit(bit_index); |
| } |
| ALWAYS_INLINE bool Test(mirror::Object* obj) const { |
| return MemRangeBitmap::Test(reinterpret_cast<uintptr_t>(obj)); |
| } |
| ALWAYS_INLINE uintptr_t GetWord(size_t index) const { |
| static_assert(kBitmapWordsPerVectorWord == 1); |
| return Bitmap::Begin()[index * kBitmapWordsPerVectorWord]; |
| } |
| }; |
| |
| // For a given object address in pre-compact space, return the corresponding |
| // address in the from-space, where heap pages are relocated in the compaction |
| // pause. |
| mirror::Object* GetFromSpaceAddr(mirror::Object* obj) const { |
| DCHECK(live_words_bitmap_->HasAddress(obj)) << " obj=" << obj; |
| return reinterpret_cast<mirror::Object*>(reinterpret_cast<uintptr_t>(obj) |
| + from_space_slide_diff_); |
| } |
| |
| void InitializePhase(); |
| void FinishPhase() REQUIRES(!Locks::mutator_lock_, !Locks::heap_bitmap_lock_); |
| void MarkingPhase() REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::heap_bitmap_lock_); |
| void CompactionPhase() REQUIRES_SHARED(Locks::mutator_lock_); |
| |
| void SweepSystemWeaks(Thread* self, Runtime* runtime, const bool paused) |
| REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(!Locks::heap_bitmap_lock_); |
| // Update the reference at given offset in the given object with post-compact |
| // address. |
| ALWAYS_INLINE void UpdateRef(mirror::Object* obj, MemberOffset offset) |
| REQUIRES_SHARED(Locks::mutator_lock_); |
| |
| // Verify that the gc-root is updated only once. Returns false if the update |
| // shouldn't be done. |
| ALWAYS_INLINE bool VerifyRootSingleUpdate(void* root, |
| mirror::Object* old_ref, |
| const RootInfo& info) |
| REQUIRES_SHARED(Locks::mutator_lock_); |
| // Update the given root with post-compact address. |
| ALWAYS_INLINE void UpdateRoot(mirror::CompressedReference<mirror::Object>* root, |
| const RootInfo& info = RootInfo(RootType::kRootUnknown)) |
| REQUIRES_SHARED(Locks::mutator_lock_); |
| ALWAYS_INLINE void UpdateRoot(mirror::Object** root, |
| const RootInfo& info = RootInfo(RootType::kRootUnknown)) |
| REQUIRES_SHARED(Locks::mutator_lock_); |
| // Given the pre-compact address, the function returns the post-compact |
| // address of the given object. |
| ALWAYS_INLINE mirror::Object* PostCompactAddress(mirror::Object* old_ref) const |
| REQUIRES_SHARED(Locks::mutator_lock_); |
| // Compute post-compact address of an object in moving space. This function |
| // assumes that old_ref is in moving space. |
| ALWAYS_INLINE mirror::Object* PostCompactAddressUnchecked(mirror::Object* old_ref) const |
| REQUIRES_SHARED(Locks::mutator_lock_); |
| // Compute the new address for an object which was allocated prior to starting |
| // this GC cycle. |
| ALWAYS_INLINE mirror::Object* PostCompactOldObjAddr(mirror::Object* old_ref) const |
| REQUIRES_SHARED(Locks::mutator_lock_); |
| // Compute the new address for an object which was black allocated during this |
| // GC cycle. |
| ALWAYS_INLINE mirror::Object* PostCompactBlackObjAddr(mirror::Object* old_ref) const |
| REQUIRES_SHARED(Locks::mutator_lock_); |
| // Identify immune spaces and reset card-table, mod-union-table, and mark |
| // bitmaps. |
| void BindAndResetBitmaps() REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| |
| // Perform one last round of marking, identifying roots from dirty cards |
| // during a stop-the-world (STW) pause. |
| void MarkingPause() REQUIRES(Locks::mutator_lock_, !Locks::heap_bitmap_lock_); |
| // Perform stop-the-world pause prior to concurrent compaction. |
| void PreCompactionPhase() REQUIRES(!Locks::mutator_lock_); |
| // Compute offsets (in chunk_info_vec_) and other data structures required |
| // during concurrent compaction. |
| void PrepareForCompaction() REQUIRES_SHARED(Locks::mutator_lock_); |
| |
| // Copy kPageSize live bytes starting from 'offset' (within the moving space), |
| // which must be within 'obj', into the kPageSize sized memory pointed by 'addr'. |
| // Then update the references within the copied objects. The boundary objects are |
| // partially updated such that only the references that lie in the page are updated. |
| // This is necessary to avoid cascading userfaults. |
| void CompactPage(mirror::Object* obj, uint32_t offset, uint8_t* addr) |
| REQUIRES_SHARED(Locks::mutator_lock_); |
| // Compact the bump-pointer space. |
| void CompactMovingSpace() REQUIRES_SHARED(Locks::mutator_lock_); |
| // Update all the objects in the given non-moving space page. 'first' object |
| // could have started in some preceding page. |
| void UpdateNonMovingPage(mirror::Object* first, uint8_t* page) |
| REQUIRES_SHARED(Locks::mutator_lock_); |
| // Update all the references in the non-moving space. |
| void UpdateNonMovingSpace() REQUIRES_SHARED(Locks::mutator_lock_); |
| |
| // For all the pages in non-moving space, find the first object that overlaps |
| // with the pages' start address, and store in first_objs_non_moving_space_ array. |
| void InitNonMovingSpaceFirstObjects() REQUIRES_SHARED(Locks::mutator_lock_); |
| // In addition to the first-objects for every post-compact moving space page, |
| // also find offsets within those objects from where the contents should be |
| // copied to the page. The offsets are relative to the moving-space's |
| // beginning. Store the computed first-object and offset in first_objs_moving_space_ |
| // and pre_compact_offset_moving_space_ respectively. |
| void InitMovingSpaceFirstObjects(const size_t vec_len) REQUIRES_SHARED(Locks::mutator_lock_); |
| |
| // Gather the info related to black allocations from bump-pointer space to |
| // enable concurrent sliding of these pages. |
| void UpdateMovingSpaceBlackAllocations() REQUIRES(Locks::mutator_lock_, Locks::heap_bitmap_lock_); |
| // Update first-object info from allocation-stack for non-moving space black |
| // allocations. |
| void UpdateNonMovingSpaceBlackAllocations() REQUIRES(Locks::mutator_lock_, Locks::heap_bitmap_lock_); |
| |
| // Slides (retain the empty holes, which are usually part of some in-use TLAB) |
| // black page in the moving space. 'first_obj' is the object that overlaps with |
| // the first byte of the page being slid. pre_compact_page is the pre-compact |
| // address of the page being slid. 'page_idx' is used to fetch the first |
| // allocated chunk's size and next page's first_obj. 'dest' is the kPageSize |
| // sized memory where the contents would be copied. |
| void SlideBlackPage(mirror::Object* first_obj, |
| const size_t page_idx, |
| uint8_t* const pre_compact_page, |
| uint8_t* dest) |
| REQUIRES_SHARED(Locks::mutator_lock_); |
| |
| // Perform reference-processing and the likes before sweeping the non-movable |
| // spaces. |
| void ReclaimPhase() REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::heap_bitmap_lock_); |
| |
| // Mark GC-roots (except from immune spaces and thread-stacks) during a STW pause. |
| void ReMarkRoots(Runtime* runtime) REQUIRES(Locks::mutator_lock_, Locks::heap_bitmap_lock_); |
| // Concurrently mark GC-roots, except from immune spaces. |
| void MarkRoots(VisitRootFlags flags) REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| // Collect thread stack roots via a checkpoint. |
| void MarkRootsCheckpoint(Thread* self, Runtime* runtime) REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| // Second round of concurrent marking. Mark all gray objects that got dirtied |
| // since the first round. |
| void PreCleanCards() REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_); |
| |
| void MarkNonThreadRoots(Runtime* runtime) REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| void MarkConcurrentRoots(VisitRootFlags flags, Runtime* runtime) |
| REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_); |
| |
| // Traverse through the reachable objects and mark them. |
| void MarkReachableObjects() REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| // Scan (only) immune spaces looking for references into the garbage collected |
| // spaces. |
| void UpdateAndMarkModUnion() REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| // Scan mod-union and card tables, covering all the spaces, to identify dirty objects. |
| // These are in 'minimum age' cards, which is 'kCardAged' in case of concurrent (second round) |
| // marking and kCardDirty during the STW pause. |
| void ScanDirtyObjects(bool paused, uint8_t minimum_age) REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| // Recursively mark dirty objects. Invoked both concurrently as well in a STW |
| // pause in PausePhase(). |
| void RecursiveMarkDirtyObjects(bool paused, uint8_t minimum_age) |
| REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| // Go through all the objects in the mark-stack until it's empty. |
| void ProcessMarkStack() override REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| void ExpandMarkStack() REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| |
| // Scan object for references. If kUpdateLivewords is true then set bits in |
| // the live-words bitmap and add size to chunk-info. |
| template <bool kUpdateLiveWords> |
| void ScanObject(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| // Push objects to the mark-stack right after successfully marking objects. |
| void PushOnMarkStack(mirror::Object* obj) |
| REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| |
| // Update the live-words bitmap as well as add the object size to the |
| // chunk-info vector. Both are required for computation of post-compact addresses. |
| void UpdateLivenessInfo(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_); |
| |
| void ProcessReferences(Thread* self) |
| REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(!Locks::heap_bitmap_lock_); |
| |
| void MarkObjectNonNull(mirror::Object* obj, |
| mirror::Object* holder = nullptr, |
| MemberOffset offset = MemberOffset(0)) |
| REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| |
| void MarkObject(mirror::Object* obj, mirror::Object* holder, MemberOffset offset) |
| REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| |
| template <bool kParallel> |
| bool MarkObjectNonNullNoPush(mirror::Object* obj, |
| mirror::Object* holder = nullptr, |
| MemberOffset offset = MemberOffset(0)) |
| REQUIRES(Locks::heap_bitmap_lock_) |
| REQUIRES_SHARED(Locks::mutator_lock_); |
| |
| void Sweep(bool swap_bitmaps) REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| void SweepLargeObjects(bool swap_bitmaps) REQUIRES_SHARED(Locks::mutator_lock_) |
| REQUIRES(Locks::heap_bitmap_lock_); |
| |
| // For checkpoints |
| Barrier gc_barrier_; |
| // Every object inside the immune spaces is assumed to be marked. |
| ImmuneSpaces immune_spaces_; |
| // Required only when mark-stack is accessed in shared mode, which happens |
| // when collecting thread-stack roots using checkpoint. |
| Mutex mark_stack_lock_; |
| accounting::ObjectStack* mark_stack_; |
| // Special bitmap wherein all the bits corresponding to an object are set. |
| // TODO: make LiveWordsBitmap encapsulated in this class rather than a |
| // pointer. We tend to access its members in performance-sensitive |
| // code-path. Also, use a single MemMap for all the GC's data structures, |
| // which we will clear in the end. This would help in limiting the number of |
| // VMAs that get created in the kernel. |
| std::unique_ptr<LiveWordsBitmap<kAlignment>> live_words_bitmap_; |
| // Track GC-roots updated so far in a GC-cycle. This is to confirm that no |
| // GC-root is updated twice. |
| // TODO: Must be replaced with an efficient mechanism eventually. Or ensure |
| // that double updation doesn't happen in the first place. |
| std::unordered_set<void*> updated_roots_; |
| MemMap from_space_map_; |
| // Any array of live-bytes in logical chunks of kOffsetChunkSize size |
| // in the 'to-be-compacted' space. |
| MemMap info_map_; |
| // The main space bitmap |
| accounting::ContinuousSpaceBitmap* current_space_bitmap_; |
| accounting::ContinuousSpaceBitmap* non_moving_space_bitmap_; |
| space::ContinuousSpace* non_moving_space_; |
| space::BumpPointerSpace* const bump_pointer_space_; |
| Thread* thread_running_gc_; |
| size_t vector_length_; |
| size_t live_stack_freeze_size_; |
| |
| // For every page in the to-space (post-compact heap) we need to know the |
| // first object from which we must compact and/or update references. This is |
| // for both non-moving and moving space. Additionally, for the moving-space, |
| // we also need the offset within the object from where we need to start |
| // copying. |
| // chunk_info_vec_ holds live bytes for chunks during marking phase. After |
| // marking we perform an exclusive scan to compute offset for every chunk. |
| uint32_t* chunk_info_vec_; |
| // Array holding offset within first-object from where contents should be |
| // copied for a given post-compact page. |
| // For black-alloc pages, the array stores first-chunk-size. |
| union { |
| uint32_t* pre_compact_offset_moving_space_; |
| uint32_t* black_alloc_pages_first_chunk_size_; |
| }; |
| // first_objs_moving_space_[i] is the address of the first object for the ith page |
| ObjReference* first_objs_moving_space_; |
| ObjReference* first_objs_non_moving_space_; |
| size_t non_moving_first_objs_count_; |
| // Number of first-objects in the moving space. |
| size_t moving_first_objs_count_; |
| // Number of pages consumed by black objects. |
| size_t black_page_count_; |
| |
| uint8_t* from_space_begin_; |
| // moving-space's end pointer at the marking pause. All allocations beyond |
| // this will be considered black in the current GC cycle. Aligned up to page |
| // size. |
| uint8_t* black_allocations_begin_; |
| // End of compacted space. Use for computing post-compact addr of black |
| // allocated objects. Aligned up to page size. |
| uint8_t* post_compact_end_; |
| // Cache (black_allocations_begin_ - post_compact_end_) for post-compact |
| // address computations. |
| ptrdiff_t black_objs_slide_diff_; |
| // Cache (from_space_begin_ - bump_pointer_space_->Begin()) so that we can |
| // compute from-space address of a given pre-comapct addr efficiently. |
| ptrdiff_t from_space_slide_diff_; |
| |
| // TODO: Remove once an efficient mechanism to deal with double root updation |
| // is incorporated. |
| void* stack_addr_; |
| void* stack_end_; |
| // Set to true when compacting starts. |
| bool compacting_; |
| |
| class VerifyRootMarkedVisitor; |
| class ScanObjectVisitor; |
| class CheckpointMarkThreadRoots; |
| template<size_t kBufferSize> class ThreadRootsVisitor; |
| class CardModifiedVisitor; |
| class RefFieldsVisitor; |
| template <bool kCheckBegin, bool kCheckEnd> class RefsUpdateVisitor; |
| class StackRefsUpdateVisitor; |
| class CompactionPauseCallback; |
| class ImmuneSpaceUpdateObjVisitor; |
| |
| DISALLOW_IMPLICIT_CONSTRUCTORS(MarkCompact); |
| }; |
| |
| } // namespace collector |
| } // namespace gc |
| } // namespace art |
| |
| #endif // ART_RUNTIME_GC_COLLECTOR_MARK_COMPACT_H_ |