[heap] Instrument mark-compact to track retaining paths for debugging.

This patch adds a new intrinsic: %DebugTrackRetainingPath(object).
Calling the intrinsic in JS code saves a weak reference to the given
object in GC internal table of tracked objects.

Each subsequent full GC prints to stdout the retaining path for each
tracked object (if it is still alive). The retaining path is the real
path that the marker took from the root set to the tracked object.

This is useful for investigating of memory leaks:
1) Add %DebugTrackRetainingPath(leaking_object) in JS code.
For example:
  function foo() {
    let x = { bar: "bar"};
    %DebugTrackRetainingPath(x);
    return () => { return x; }
  }

  let closure = foo();
  gc();

2) Run d8 with --allow-natives-syntax --track-retaining-path --expose-gc.

3) Check the retaining path in stdout.

For more detailed inspection, run d8 in gdb and set breakpoint in

v8: :internal::Heap::PrintRetainingPath.
Change-Id: I01a0faac1e009bc6c321fa75613900b49d2b036f
Reviewed-on: https://chromium-review.googlesource.com/575972
Commit-Queue: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#46766}
This commit is contained in:
Ulan Degenbaev 2017-07-19 15:27:45 +02:00 committed by Commit Bot
parent 7cfd0c249e
commit 40c34606a7
13 changed files with 213 additions and 37 deletions

View File

@ -650,11 +650,15 @@ DEFINE_BOOL(track_gc_object_stats, false,
"track object counts and memory usage")
DEFINE_BOOL(trace_gc_object_stats, false,
"trace object counts and memory usage")
DEFINE_BOOL(track_retaining_path, false,
"enable support for tracking retaining path")
DEFINE_INT(gc_stats, 0, "Used by tracing internally to enable gc statistics")
DEFINE_IMPLICATION(trace_gc_object_stats, track_gc_object_stats)
DEFINE_VALUE_IMPLICATION(track_gc_object_stats, gc_stats, 1)
DEFINE_VALUE_IMPLICATION(trace_gc_object_stats, gc_stats, 1)
DEFINE_NEG_IMPLICATION(trace_gc_object_stats, incremental_marking)
DEFINE_NEG_IMPLICATION(track_retaining_path, incremental_marking)
DEFINE_NEG_IMPLICATION(track_retaining_path, concurrent_marking)
DEFINE_BOOL(track_detached_contexts, true,
"track native contexts that are expected to be garbage collected")
DEFINE_BOOL(trace_detached_contexts, false,

View File

@ -407,6 +407,99 @@ void Heap::ReportStatisticsAfterGC() {
}
}
void Heap::AddRetainingPathTarget(Handle<HeapObject> object) {
if (!FLAG_track_retaining_path) {
PrintF("Retaining path tracking requires --trace-retaining-path\n");
} else {
Handle<WeakFixedArray> array = WeakFixedArray::Add(
handle(retaining_path_targets(), isolate()), object);
set_retaining_path_targets(*array);
}
}
bool Heap::IsRetainingPathTarget(HeapObject* object) {
WeakFixedArray::Iterator it(retaining_path_targets());
HeapObject* target;
while ((target = it.Next<HeapObject>()) != nullptr) {
if (target == object) return true;
}
return false;
}
namespace {
const char* RootToString(Root root) {
switch (root) {
#define ROOT_CASE(root_id, ignore, description) \
case Root::root_id: \
return description;
ROOT_ID_LIST(ROOT_CASE)
#undef ROOT_CASE
case Root::kCodeFlusher:
return "(Code flusher)";
case Root::kPartialSnapshotCache:
return "(Partial snapshot cache)";
case Root::kWeakCollections:
return "(Weak collections)";
case Root::kWrapperTracing:
return "(Wrapper tracing)";
case Root::kUnknown:
return "(Unknown)";
}
UNREACHABLE();
return nullptr;
}
} // namespace
void Heap::PrintRetainingPath(HeapObject* target) {
PrintF("\n\n\n");
PrintF("#################################################\n");
PrintF("Retaining path for %p:\n", static_cast<void*>(target));
HeapObject* object = target;
std::vector<HeapObject*> retaining_path;
Root root = Root::kUnknown;
while (true) {
retaining_path.push_back(object);
if (retainer_.count(object)) {
object = retainer_[object];
} else {
if (retaining_root_.count(object)) {
root = retaining_root_[object];
}
break;
}
}
int distance = static_cast<int>(retaining_path.size());
for (auto object : retaining_path) {
PrintF("\n");
PrintF("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n");
PrintF("Distance from root %d: ", distance);
object->ShortPrint();
PrintF("\n");
#ifdef OBJECT_PRINT
object->Print();
PrintF("\n");
#endif
--distance;
}
PrintF("\n");
PrintF("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n");
PrintF("Root: %s\n", RootToString(root));
PrintF("-------------------------------------------------\n");
}
void Heap::AddRetainer(HeapObject* retainer, HeapObject* object) {
retainer_[object] = retainer;
if (IsRetainingPathTarget(object)) {
PrintRetainingPath(object);
}
}
void Heap::AddRetainingRoot(Root root, HeapObject* object) {
retaining_root_[object] = root;
if (IsRetainingPathTarget(object)) {
PrintRetainingPath(object);
}
}
void Heap::IncrementDeferredCount(v8::Isolate::UseCounterFeature feature) {
deferred_counters_[feature]++;
@ -452,6 +545,10 @@ void Heap::GarbageCollectionPrologue() {
}
CheckNewSpaceExpansionCriteria();
UpdateNewSpaceAllocationCounter();
if (FLAG_track_retaining_path) {
retainer_.clear();
retaining_root_.clear();
}
}
size_t Heap::SizeOfObjects() {
@ -966,6 +1063,7 @@ void Heap::CollectAllAvailableGarbage(GarbageCollectionReason gc_reason) {
break;
}
}
set_current_gc_flags(kNoGCFlags);
new_space_->Shrink();
UncommitFromSpace();
@ -2859,6 +2957,7 @@ void Heap::CreateInitialObjects() {
set_detached_contexts(empty_fixed_array());
set_retained_maps(ArrayList::cast(empty_fixed_array()));
set_retaining_path_targets(undefined_value());
set_weak_object_to_code_table(*WeakHashTable::New(isolate(), 16, TENURED));
@ -2994,6 +3093,7 @@ bool Heap::RootCanBeWrittenAfterInitialization(Heap::RootListIndex root_index) {
case kWeakObjectToCodeTableRootIndex:
case kWeakNewSpaceObjectToCodeListRootIndex:
case kRetainedMapsRootIndex:
case kRetainingPathTargetsRootIndex:
case kCodeCoverageListRootIndex:
case kNoScriptSharedFunctionInfosRootIndex:
case kWeakStackTraceListRootIndex:
@ -4508,7 +4608,6 @@ bool Heap::PerformIdleTimeAction(GCIdleTimeAction action,
return result;
}
void Heap::IdleNotificationEpilogue(GCIdleTimeAction action,
GCIdleTimeHeapState heap_state,
double start_ms, double deadline_in_ms) {
@ -5908,7 +6007,7 @@ void Heap::RegisterExternallyReferencedObject(Object** object) {
incremental_marking()->WhiteToGreyAndPush(heap_object);
} else {
DCHECK(mark_compact_collector()->in_use());
mark_compact_collector()->MarkObject(heap_object);
mark_compact_collector()->MarkExternallyReferencedObject(heap_object);
}
}

View File

@ -203,6 +203,7 @@ using v8::MemoryPressureLevel;
V(FixedArray, materialized_objects, MaterializedObjects) \
V(FixedArray, microtask_queue, MicrotaskQueue) \
V(FixedArray, detached_contexts, DetachedContexts) \
V(HeapObject, retaining_path_targets, RetainingPathTargets) \
V(ArrayList, retained_maps, RetainedMaps) \
V(WeakHashTable, weak_object_to_code_table, WeakObjectToCodeTable) \
/* weak_new_space_object_to_code_list is an array of weak cells, where */ \
@ -1480,8 +1481,16 @@ class Heap {
void MergeAllocationSitePretenuringFeedback(
const base::HashMap& local_pretenuring_feedback);
// =============================================================================
// ===========================================================================
// Retaining path tracking. ==================================================
// ===========================================================================
// Adds the given object to the weak table of retaining path targets.
// On each GC if the marker discovers the object, it will print the retaining
// path. This requires --track-retaining-path flag.
void AddRetainingPathTarget(Handle<HeapObject> object);
// =============================================================================
#ifdef VERIFY_HEAP
// Verify the heap is in its normal state before or after a GC.
void Verify();
@ -2142,9 +2151,16 @@ class Heap {
MUST_USE_RESULT AllocationResult
AllocateCode(int object_size, bool immovable);
void set_force_oom(bool value) { force_oom_ = value; }
// ===========================================================================
// Retaining path tracing ====================================================
// ===========================================================================
void set_force_oom(bool value) { force_oom_ = value; }
void AddRetainer(HeapObject* retainer, HeapObject* object);
void AddRetainingRoot(Root root, HeapObject* object);
bool IsRetainingPathTarget(HeapObject* object);
void PrintRetainingPath(HeapObject* object);
// The amount of external memory registered through the API.
int64_t external_memory_;
@ -2382,12 +2398,17 @@ class Heap {
HeapObject* pending_layout_change_object_;
std::map<HeapObject*, HeapObject*> retainer_;
std::map<HeapObject*, Root> retaining_root_;
// Classes in "heap" can be friends.
friend class AlwaysAllocateScope;
friend class ConcurrentMarking;
friend class GCCallbacksScope;
friend class GCTracer;
friend class HeapIterator;
template <typename ConcreteVisitor>
friend class MarkingVisitor;
friend class IdleScavengeObserver;
friend class IncrementalMarking;
friend class IncrementalMarkingJob;

View File

@ -294,7 +294,7 @@ class IncrementalMarkingMarkingVisitor final
Object* target = *p;
if (target->IsHeapObject()) {
collector_->RecordSlot(host, p, target);
MarkObject(target);
MarkObject(host, target);
}
}
@ -304,13 +304,13 @@ class IncrementalMarkingMarkingVisitor final
Object* target = *p;
if (target->IsHeapObject()) {
collector_->RecordSlot(host, p, target);
MarkObject(target);
MarkObject(host, target);
}
}
}
// Marks the object grey and pushes it on the marking stack.
V8_INLINE void MarkObject(Object* obj) {
V8_INLINE void MarkObject(HeapObject* host, Object* obj) {
incremental_marking_->WhiteToGreyAndPush(HeapObject::cast(obj));
}

View File

@ -21,10 +21,23 @@ void MarkCompactCollector::PushBlack(HeapObject* obj) {
}
}
void MarkCompactCollector::MarkObject(HeapObject* obj) {
void MarkCompactCollector::MarkObject(HeapObject* host, HeapObject* obj) {
if (ObjectMarking::WhiteToBlack<AccessMode::NON_ATOMIC>(
obj, MarkingState::Internal(obj))) {
PushBlack(obj);
if (V8_UNLIKELY(FLAG_track_retaining_path)) {
heap_->AddRetainer(host, obj);
}
}
}
void MarkCompactCollector::MarkExternallyReferencedObject(HeapObject* obj) {
if (ObjectMarking::WhiteToBlack<AccessMode::NON_ATOMIC>(
obj, MarkingState::Internal(obj))) {
PushBlack(obj);
if (V8_UNLIKELY(FLAG_track_retaining_path)) {
heap_->AddRetainingRoot(Root::kWrapperTracing, obj);
}
}
}

View File

@ -1052,7 +1052,8 @@ class MarkCompactMarkingVisitor final
Object** end) final {
// Mark all objects pointed to in [start, end).
const int kMinRangeForMarkingRecursion = 64;
if (end - start >= kMinRangeForMarkingRecursion) {
if (end - start >= kMinRangeForMarkingRecursion &&
V8_LIKELY(!FLAG_track_retaining_path)) {
if (VisitUnmarkedObjects(host, start, end)) return;
// We are close to a stack overflow, so just mark the objects.
}
@ -1062,8 +1063,8 @@ class MarkCompactMarkingVisitor final
}
// Marks the object black and pushes it on the marking stack.
V8_INLINE void MarkObject(HeapObject* object) {
collector_->MarkObject(object);
V8_INLINE void MarkObject(HeapObject* host, HeapObject* object) {
collector_->MarkObject(host, object);
}
// Marks the object black without pushing it on the marking stack. Returns
@ -1076,7 +1077,7 @@ class MarkCompactMarkingVisitor final
if (!(*p)->IsHeapObject()) return;
HeapObject* target_object = HeapObject::cast(*p);
collector_->RecordSlot(host, p, target_object);
collector_->MarkObject(target_object);
collector_->MarkObject(host, target_object);
}
protected:
@ -1106,7 +1107,7 @@ class MarkCompactMarkingVisitor final
Map* map = obj->map();
ObjectMarking::WhiteToBlack(obj, MarkingState::Internal(obj));
// Mark the map pointer and the body.
collector_->MarkObject(map);
collector_->MarkObject(obj, map);
Visit(map, obj);
}
}
@ -1132,19 +1133,20 @@ class MarkCompactCollector::RootMarkingVisitor : public ObjectVisitor,
: collector_(heap->mark_compact_collector()), visitor_(collector_) {}
void VisitPointer(HeapObject* host, Object** p) override {
MarkObjectByPointer(p);
MarkObjectByPointer(host, p);
}
void VisitPointers(HeapObject* host, Object** start, Object** end) override {
for (Object** p = start; p < end; p++) MarkObjectByPointer(p);
for (Object** p = start; p < end; p++) MarkObjectByPointer(host, p);
}
void VisitRootPointer(Root root, Object** p) override {
MarkObjectByPointer(p);
MarkObjectByPointer(nullptr, p, root);
}
void VisitRootPointers(Root root, Object** start, Object** end) override {
for (Object** p = start; p < end; p++) MarkObjectByPointer(p);
for (Object** p = start; p < end; p++)
MarkObjectByPointer(nullptr, p, root);
}
// Skip the weak next code link in a code object, which is visited in
@ -1152,16 +1154,24 @@ class MarkCompactCollector::RootMarkingVisitor : public ObjectVisitor,
void VisitNextCodeLink(Code* host, Object** p) override {}
private:
void MarkObjectByPointer(Object** p) {
void MarkObjectByPointer(HeapObject* host, Object** p,
Root root = Root::kUnknown) {
if (!(*p)->IsHeapObject()) return;
HeapObject* object = HeapObject::cast(*p);
if (ObjectMarking::WhiteToBlack<AccessMode::NON_ATOMIC>(
object, MarkingState::Internal(object))) {
if (V8_UNLIKELY(FLAG_track_retaining_path)) {
if (host) {
object->GetHeap()->AddRetainer(host, object);
} else {
object->GetHeap()->AddRetainingRoot(root, object);
}
}
Map* map = object->map();
// Mark the map pointer and body, and push them on the marking stack.
collector_->MarkObject(map);
collector_->MarkObject(object, map);
visitor_.Visit(map, object);
// Mark all the objects reachable from the map and body. May leave
// overflowed objects in the heap.
@ -1961,7 +1971,7 @@ void MarkCompactCollector::EmptyMarkingWorklist() {
object, MarkingState::Internal(object))));
Map* map = object->map();
MarkObject(map);
MarkObject(object, map);
visitor.Visit(map, object);
}
DCHECK(marking_worklist()->IsEmpty());

View File

@ -681,7 +681,10 @@ class MarkCompactCollector final : public MarkCompactCollectorBase {
// Marks the object black and pushes it on the marking stack.
// This is for non-incremental marking only.
V8_INLINE void MarkObject(HeapObject* obj);
V8_INLINE void MarkObject(HeapObject* host, HeapObject* obj);
// Used by wrapper tracing.
V8_INLINE void MarkExternallyReferencedObject(HeapObject* obj);
// Mark the heap roots and all objects reachable from them.
void MarkRoots(RootMarkingVisitor* visitor);

View File

@ -321,6 +321,9 @@ void MarkingVisitor<ConcreteVisitor>::MarkMapContents(Map* map) {
// just mark the entire descriptor array.
if (!map->is_prototype_map()) {
DescriptorArray* descriptors = map->instance_descriptors();
if (V8_UNLIKELY(FLAG_track_retaining_path)) {
heap_->AddRetainer(map, descriptors);
}
if (visitor->MarkObjectWithoutPush(descriptors) &&
descriptors->length() > 0) {
visitor->VisitPointers(descriptors, descriptors->GetFirstElementAddress(),
@ -392,7 +395,7 @@ void MarkingVisitor<ConcreteVisitor>::VisitCodeEntry(JSFunction* host,
ConcreteVisitor* visitor = static_cast<ConcreteVisitor*>(this);
Code* code = Code::cast(Code::GetObjectFromEntryAddress(entry_address));
collector_->RecordCodeEntrySlot(host, entry_address, code);
visitor->MarkObject(code);
visitor->MarkObject(host, code);
}
template <typename ConcreteVisitor>
@ -403,7 +406,7 @@ void MarkingVisitor<ConcreteVisitor>::VisitEmbeddedPointer(Code* host,
HeapObject* object = HeapObject::cast(rinfo->target_object());
collector_->RecordRelocSlot(host, rinfo, object);
if (!host->IsWeakObject(object)) {
visitor->MarkObject(object);
visitor->MarkObject(host, object);
}
}
@ -415,7 +418,7 @@ void MarkingVisitor<ConcreteVisitor>::VisitCellPointer(Code* host,
Cell* cell = rinfo->target_cell();
collector_->RecordRelocSlot(host, rinfo, cell);
if (!host->IsWeakObject(cell)) {
visitor->MarkObject(cell);
visitor->MarkObject(host, cell);
}
}
@ -427,7 +430,7 @@ void MarkingVisitor<ConcreteVisitor>::VisitDebugTarget(Code* host,
rinfo->IsPatchedDebugBreakSlotSequence());
Code* target = Code::GetCodeFromTargetAddress(rinfo->debug_call_address());
collector_->RecordRelocSlot(host, rinfo, target);
visitor->MarkObject(target);
visitor->MarkObject(host, target);
}
template <typename ConcreteVisitor>
@ -437,7 +440,7 @@ void MarkingVisitor<ConcreteVisitor>::VisitCodeTarget(Code* host,
DCHECK(RelocInfo::IsCodeTarget(rinfo->rmode()));
Code* target = Code::GetCodeFromTargetAddress(rinfo->target_address());
collector_->RecordRelocSlot(host, rinfo, target);
visitor->MarkObject(target);
visitor->MarkObject(host, target);
}
template <typename ConcreteVisitor>
@ -448,7 +451,7 @@ void MarkingVisitor<ConcreteVisitor>::VisitCodeAgeSequence(Code* host,
Code* target = rinfo->code_age_stub();
DCHECK_NOT_NULL(target);
collector_->RecordRelocSlot(host, rinfo, target);
visitor->MarkObject(target);
visitor->MarkObject(host, target);
}
} // namespace internal

View File

@ -269,6 +269,7 @@ void ByteArray::ByteArrayPrint(std::ostream& os) { // NOLINT
void BytecodeArray::BytecodeArrayPrint(std::ostream& os) { // NOLINT
HeapObject::PrintHeader(os, "BytecodeArray");
Disassemble(os);
}
@ -1090,9 +1091,22 @@ void JSFunction::JSFunctionPrint(std::ostream& os) { // NOLINT
os << "\n - bytecode = " << shared()->bytecode_array();
}
}
shared()->PrintSourceCode(os);
JSObjectPrintBody(os, this);
}
void SharedFunctionInfo::PrintSourceCode(std::ostream& os) {
if (HasSourceCode()) {
os << "\n - source code = ";
String* source = String::cast(Script::cast(script())->source());
int start = start_position();
int length = end_position() - start;
std::unique_ptr<char[]> source_string = source->ToCString(
DISALLOW_NULLS, FAST_STRING_TRAVERSAL, start, length, NULL);
os << source_string.get();
}
}
void SharedFunctionInfo::SharedFunctionInfoPrint(std::ostream& os) { // NOLINT
HeapObject::PrintHeader(os, "SharedFunctionInfo");
os << "\n - name = ";
@ -1113,15 +1127,7 @@ void SharedFunctionInfo::SharedFunctionInfoPrint(std::ostream& os) { // NOLINT
if (HasBytecodeArray()) {
os << "\n - bytecode_array = " << bytecode_array();
}
if (HasSourceCode()) {
os << "\n - source code = ";
String* source = String::cast(Script::cast(script())->source());
int start = start_position();
int length = end_position() - start;
std::unique_ptr<char[]> source_string = source->ToCString(
DISALLOW_NULLS, FAST_STRING_TRAVERSAL, start, length, NULL);
os << source_string.get();
}
PrintSourceCode(os);
// Script files are often large, hard to read.
// os << "\n - script =";
// script()->Print(os);

View File

@ -391,6 +391,9 @@ class SharedFunctionInfo : public HeapObject {
// Dispatched behavior.
DECL_PRINTER(SharedFunctionInfo)
DECL_VERIFIER(SharedFunctionInfo)
#ifdef OBJECT_PRINT
void PrintSourceCode(std::ostream& os);
#endif
void ResetForNewContext(int new_ic_age);

View File

@ -612,6 +612,17 @@ RUNTIME_FUNCTION(Runtime_DebugTrace) {
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_DebugTrackRetainingPath) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
if (!FLAG_track_retaining_path) {
PrintF("DebugTrackRetainingPath requires --track-retaining-path flag.\n");
} else {
CONVERT_ARG_HANDLE_CHECKED(HeapObject, object, 0);
isolate->heap()->AddRetainingPathTarget(object);
}
return isolate->heap()->undefined_value();
}
// This will not allocate (flatten the string), but it may run
// very slowly for very deeply nested ConsStrings. For debugging use only.

View File

@ -569,6 +569,7 @@ namespace internal {
F(SetAllocationTimeout, -1 /* 2 || 3 */, 1) \
F(DebugPrint, 1, 1) \
F(DebugTrace, 0, 1) \
F(DebugTrackRetainingPath, 1, 1) \
F(GetExceptionDetails, 1, 1) \
F(GlobalPrint, 1, 1) \
F(SystemBreak, 0, 1) \

View File

@ -48,7 +48,9 @@ enum class Root {
// TODO(ulan): Merge with the ROOT_ID_LIST.
kCodeFlusher,
kPartialSnapshotCache,
kWeakCollections
kWeakCollections,
kWrapperTracing,
kUnknown
};
// Abstract base class for visiting, and optionally modifying, the