前言:V8 Heap Profiler 用于收集哪些代码分析了多少内存的信息。本文介绍 V8 中关于这部分的实现,代码来自 V8 10.2。
入口函数是 StartSamplingHeapProfiler。
bool StartSamplingHeapProfiler(uint64_t sample_interval, int stack_depth, v8::HeapProfiler::SamplingFlags);
主要的参数是 sample_interval。
bool HeapProfiler::StartSamplingHeapProfiler(
uint64_t sample_interval, int stack_depth,
v8::HeapProfiler::SamplingFlags flags) {
if (sampling_heap_profiler_.get()) {
return false;
}
sampling_heap_profiler_.reset(new SamplingHeapProfiler(
heap(), names_.get(), sample_interval, stack_depth, flags));
return true;
}
主要逻辑是创建一个 SamplingHeapProfiler 对象。来看一下这个对象的构造函数。
SamplingHeapProfiler::SamplingHeapProfiler(
Heap* heap, StringsStorage* names, uint64_t rate, int stack_depth,
v8::HeapProfiler::SamplingFlags flags)
: isolate_(Isolate::FromHeap(heap)),
heap_(heap),
allocation_observer_(heap_, static_cast<intptr_t>(rate), rate, this,
isolate_->random_number_generator()),
names_(names),
profile_root_(nullptr, "(root)", v8::UnboundScript::kNoScriptId, 0,
next_node_id()),
stack_depth_(stack_depth),
rate_(rate),
flags_(flags) {
heap_->AddAllocationObserversToAllSpaces(&allocation_observer_,
&allocation_observer_);
}
代码比较简单,主要是初始化一些字段,其中最重要的是 allocation_observer_ 字段,该字段是一个 Observer 对象。
class Observer : public AllocationObserver {
public:
Observer(Heap* heap, intptr_t step_size, uint64_t rate,
SamplingHeapProfiler* profiler,
base::RandomNumberGenerator* random)
: AllocationObserver(step_size),
profiler_(profiler),
heap_(heap),
random_(random),
rate_(rate) {}
protected:
void Step(int bytes_allocated, Address soon_object, size_t size) override {
if (soon_object) {
profiler_->SampleObject(soon_object, size);
}
}
intptr_t GetNextStepSize() override { return GetNextSampleInterval(rate_); }
private:
intptr_t GetNextSampleInterval(uint64_t rate);
SamplingHeapProfiler* const profiler_;
Heap* const heap_;
base::RandomNumberGenerator* const random_;
uint64_t const rate_;
};
Observer 继承 AllocationObserver,AllocationObserver 是一个可以监听堆对象分配的接口。其中最重要的是 Step 方法,该方法在 V8 分配 n 个字节时被回调。创建完 Observer 对象后,V8 会把该对象通过 AddAllocationObserversToAllSpaces 注册到堆中。
void Heap::AddAllocationObserversToAllSpaces(
AllocationObserver* observer, AllocationObserver* new_space_observer) {
// 遍历各种堆,注册观察者
for (SpaceIterator it(this); it.HasNext();) {
Space* space = it.Next();
if (space == new_space()) {
space->AddAllocationObserver(new_space_observer);
} else {
space->AddAllocationObserver(observer);
}
}
}
AddAllocationObserversToAllSpaces 往新生代、老生代等堆内存管理对象中注册观察者,来看一下 AddAllocationObserver。
void Space::AddAllocationObserver(AllocationObserver* observer) {
allocation_counter_.AddAllocationObserver(observer);
}
void AllocationCounter::AddAllocationObserver(AllocationObserver* observer) {
intptr_t step_size = observer->GetNextStepSize();
size_t observer_next_counter = current_counter_ + step_size;
/*
struct AllocationObserverCounter final {
AllocationObserverCounter(AllocationObserver* observer, size_t prev_counter, size_t next_counter)
: observer_(observer),
prev_counter_(prev_counter),
next_counter_(next_counter) {}
AllocationObserver* observer_;
size_t prev_counter_;
size_t next_counter_;
};
*/
intptr_t step_size = observer->GetNextStepSize();
size_t observer_next_counter = current_counter_ + step_size;
observers_.push_back(AllocationObserverCounter(observer, current_counter_, observer_next_counter));
}
这样就完成了观察者的注册,接着看调用观察者的逻辑。具体在分配内存时会调用 InvokeAllocationObservers,比如新生代的 AllocateRawUnaligned 函数。
// soon_object: 分配的地址,object_size 分配的大小
void AllocationCounter::InvokeAllocationObservers(Address soon_object,
size_t object_size,
size_t aligned_object_size) {
bool step_run = false;
step_in_progress_ = true;
size_t step_size = 0;
// 遍历观察者
for (AllocationObserverCounter& aoc : observers_) {
if (aoc.next_counter_ - current_counter_ <= aligned_object_size) {
{
DisallowGarbageCollection no_gc;
aoc.observer_->Step(
static_cast<int>(current_counter_ - aoc.prev_counter_), soon_object,
object_size);
}
}
}
}
接着看之前注册的观察者的 Step 函数。
void Step(int bytes_allocated, Address soon_object, size_t size) override {
if (soon_object) {
profiler_->SampleObject(soon_object, size);
}
}
void SamplingHeapProfiler::SampleObject(Address soon_object, size_t size) {
DisallowGarbageCollection no_gc;
HandleScope scope(isolate_);
HeapObject heap_object = HeapObject::FromAddress(soon_object);
Handle<Object> obj(heap_object, isolate_);
Local<v8::Value> loc = v8::Utils::ToLocal(obj);
// 处理栈信息
AllocationNode* node = AddStack();
node->allocations_[size]++;
// 记录信息
auto sample = std::make_unique<Sample>(size, node, loc, this, next_sample_id());
samples_.emplace(sample.get(), std::move(sample));
}
SampleObject 首先处理了当前调用栈,这样才知道是谁申请了该内存。
SamplingHeapProfiler::AllocationNode* SamplingHeapProfiler::AddStack() {
AllocationNode* node = &profile_root_;
std::vector<SharedFunctionInfo> stack;
JavaScriptFrameIterator frame_it(isolate_);
int frames_captured = 0;
bool found_arguments_marker_frames = false;
// 还有栈且没有达到需要捕获的栈深度
while (!frame_it.done() && frames_captured < stack_depth_) {
JavaScriptFrame* frame = frame_it.frame();
// 记录栈
if (frame->unchecked_function().IsJSFunction()) {
SharedFunctionInfo shared = frame->function().shared();
stack.push_back(shared);
frames_captured++;
} else {
found_arguments_marker_frames = true;
}
frame_it.Advance();
}
// 遍历栈,并找到对应的代码信息
for (auto it = stack.rbegin(); it != stack.rend(); ++it) {
SharedFunctionInfo shared = *it;
const char* name = this->names()->GetCopy(shared.DebugNameCStr().get());
int script_id = v8::UnboundScript::kNoScriptId;
if (shared.script().IsScript()) {
Script script = Script::cast(shared.script());
script_id = script.id();
}
// 构造树(层次)结构
node = FindOrAddChildNode(node, name, script_id, shared.StartPosition());
}
return node;
}
AddStack 捕获了当前的栈信息并且构造了一个相应的树结构。紧接着可以调用 GetAllocationProfile 获取收集到的信息。
v8::AllocationProfile* SamplingHeapProfiler::GetAllocationProfile() {
std::map<int, Handle<Script>> scripts;
{
Script::Iterator iterator(isolate_);
for (Script script = iterator.Next(); !script.is_null();
script = iterator.Next()) {
scripts[script.id()] = handle(script, isolate_);
}
}
auto profile = new v8::internal::AllocationProfile();
TranslateAllocationNode(profile, &profile_root_, scripts);
profile->samples_ = BuildSamples();
return profile;
}
GetAllocationProfile 对收集到的数据进行处理。
v8::AllocationProfile::Node* SamplingHeapProfiler::TranslateAllocationNode(
AllocationProfile* profile, SamplingHeapProfiler::AllocationNode* node,
const std::map<int, Handle<Script>>& scripts) {
node->pinned_ = true;
Local<v8::String> script_name =
ToApiHandle<v8::String>(isolate_->factory()->InternalizeUtf8String(""));
int line = v8::AllocationProfile::kNoLineNumberInfo;
int column = v8::AllocationProfile::kNoColumnNumberInfo;
std::vector<v8::AllocationProfile::Allocation> allocations;
allocations.reserve(node->allocations_.size());
if (node->script_id_ != v8::UnboundScript::kNoScriptId) {
auto script_iterator = scripts.find(node->script_id_);
if (script_iterator != scripts.end()) {
Handle<Script> script = script_iterator->second;
if (script->name().IsName()) {
Name name = Name::cast(script->name());
script_name = ToApiHandle<v8::String>(
isolate_->factory()->InternalizeUtf8String(names_->GetName(name)));
}
line = 1 + Script::GetLineNumber(script, node->script_position_);
column = 1 + Script::GetColumnNumber(script, node->script_position_);
}
}
for (auto alloc : node->allocations_) {
allocations.push_back(ScaleSample(alloc.first, alloc.second));
}
profile->nodes_.push_back(v8::AllocationProfile::Node{
ToApiHandle<v8::String>(
isolate_->factory()->InternalizeUtf8String(node->name_)),
script_name, node->script_id_, node->script_position_, line, column,
node->id_, std::vector<v8::AllocationProfile::Node*>(), allocations});
v8::AllocationProfile::Node* current = &profile->nodes_.back();
for (const auto& it : node->children_) {
// 递归处理
current->children.push_back(
TranslateAllocationNode(profile, it.second.get(), scripts));
}
node->pinned_ = false;
return current;
}
然后构造 sample。
const std::vector<v8::AllocationProfile::Sample>
SamplingHeapProfiler::BuildSamples() const {
std::vector<v8::AllocationProfile::Sample> samples;
samples.reserve(samples_.size());
for (const auto& it : samples_) {
const Sample* sample = it.second.get();
samples.emplace_back(v8::AllocationProfile::Sample{
sample->owner->id_, sample->size, ScaleSample(sample->size, 1).count,
sample->sample_id});
}
return samples;
}
细节比较多,大致流程已经分析完毕,最终就拿到了 Heap Profile 的数据。