1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "ecmascript/mem/idle_gc_trigger.h"
17
18 #include <algorithm>
19
20 #include "ecmascript/mem/mem_controller.h"
21 #include "ecmascript/mem/concurrent_marker.h"
22 #include "ecmascript/mem/heap-inl.h"
23
24 namespace panda::ecmascript {
NotifyVsyncIdleStart()25 void IdleGCTrigger::NotifyVsyncIdleStart()
26 {
27 TryTriggerIdleLocalOldGC();
28 }
29
NotifyLooperIdleStart(int64_t timestamp,int idleTime)30 void IdleGCTrigger::NotifyLooperIdleStart([[maybe_unused]] int64_t timestamp, [[maybe_unused]] int idleTime)
31 {
32 LOG_ECMA_IF(optionalLogEnabled_, DEBUG) << "IdleGCTrigger: recv once looper idle time";
33 idleState_.store(true);
34 if (!TryTriggerIdleLocalOldGC()) {
35 TryTriggerIdleSharedOldGC();
36 }
37 }
38
NotifyLooperIdleEnd(int64_t timestamp)39 void IdleGCTrigger::NotifyLooperIdleEnd([[maybe_unused]] int64_t timestamp)
40 {
41 idleState_.store(false);
42 }
43
TryTriggerHandleMarkFinished()44 void IdleGCTrigger::TryTriggerHandleMarkFinished()
45 {
46 if (heap_->GetJSThread()->IsMarkFinished() && heap_->GetConcurrentMarker()->IsTriggeredConcurrentMark()
47 && !heap_->GetOnSerializeEvent() && !heap_->InSensitiveStatus()) {
48 heap_->SetCanThrowOOMError(false);
49 heap_->GetConcurrentMarker()->HandleMarkingFinished();
50 heap_->SetCanThrowOOMError(true);
51 }
52 }
53
TryTriggerLocalConcurrentMark()54 void IdleGCTrigger::TryTriggerLocalConcurrentMark()
55 {
56 if (heap_->GetConcurrentMarker()->IsEnabled() && heap_->CheckCanTriggerConcurrentMarking()) {
57 heap_->SetFullMarkRequestedState(true);
58 heap_->SetMarkType(MarkType::MARK_FULL);
59 heap_->TriggerConcurrentMarking();
60 }
61 }
62
TryTriggerIdleLocalOldGC()63 bool IdleGCTrigger::TryTriggerIdleLocalOldGC()
64 {
65 if (heap_->GetJSThread()->IsMarkFinished() &&
66 heap_->GetConcurrentMarker()->IsTriggeredConcurrentMark() &&
67 IsPossiblePostGCTask(TRIGGER_IDLE_GC_TYPE::LOCAL_REMARK)) {
68 PostIdleGCTask(TRIGGER_IDLE_GC_TYPE::LOCAL_REMARK);
69 return true;
70 }
71 if (!IsPossiblePostGCTask(TRIGGER_IDLE_GC_TYPE::LOCAL_CONCURRENT_MARK)) {
72 return false;
73 }
74 if (heap_->GetJSThread()->FullMarkRequest() && !heap_->NeedStopCollection()) {
75 heap_->GetJSThread()->ResetFullMarkRequest();
76 PostIdleGCTask(TRIGGER_IDLE_GC_TYPE::LOCAL_CONCURRENT_MARK);
77 return true;
78 }
79 if (ShouldCheckIdleOldGC<Heap>(heap_) && ReachIdleLocalOldGCThresholds() && !heap_->NeedStopCollection()) {
80 if (heap_->GetConcurrentMarker()->IsEnabled() && heap_->CheckCanTriggerConcurrentMarking()) {
81 PostIdleGCTask(TRIGGER_IDLE_GC_TYPE::LOCAL_CONCURRENT_MARK);
82 } else {
83 PostIdleGCTask(TRIGGER_IDLE_GC_TYPE::OLD_GC);
84 }
85 return true;
86 }
87 return false;
88 }
89
TryTriggerIdleSharedOldGC()90 bool IdleGCTrigger::TryTriggerIdleSharedOldGC()
91 {
92 if (ShouldCheckIdleOldGC<SharedHeap>(sHeap_) && ReachIdleSharedGCThresholds() && !heap_->NeedStopCollection()) {
93 PostIdleGCTask(TRIGGER_IDLE_GC_TYPE::SHARED_GC);
94 return true;
95 }
96 return false;
97 }
98
ReachIdleLocalOldGCThresholds()99 bool IdleGCTrigger::ReachIdleLocalOldGCThresholds()
100 {
101 bool isFullMarking = heap_->IsConcurrentFullMark() && heap_->GetJSThread()->IsMarking();
102 bool isNativeSizeLargeTrigger = isFullMarking ? false : heap_->GlobalNativeSizeLargerThanLimitForIdle();
103 if (isNativeSizeLargeTrigger) {
104 return true;
105 }
106
107 OldSpace *oldSpace = heap_->GetOldSpace();
108 HugeObjectSpace *hugeObjectSpace = heap_->GetHugeObjectSpace();
109 size_t idleSizeLimit = static_cast<size_t>(oldSpace->GetInitialCapacity() *
110 IDLE_SPACE_SIZE_LIMIT_RATE);
111 size_t currentSize = oldSpace->GetHeapObjectSize() + hugeObjectSpace->GetHeapObjectSize();
112 if (currentSize >= idleSizeLimit) {
113 return true;
114 }
115
116 size_t maxCapacity = oldSpace->GetMaximumCapacity() + oldSpace->GetOvershootSize() +
117 oldSpace->GetOutOfMemoryOvershootSize();
118 size_t currentCapacity = oldSpace->GetCommittedSize() + hugeObjectSpace->GetCommittedSize();
119 size_t idleCapacityLimit = static_cast<size_t>(maxCapacity * IDLE_SPACE_SIZE_LIMIT_RATE);
120 if (currentCapacity >= idleCapacityLimit) {
121 return true;
122 }
123
124 size_t oldSpaceAllocLimit = heap_->GetGlobalSpaceAllocLimit() + oldSpace->GetOvershootSize();
125 size_t idleOldSpaceAllocLimit = static_cast<size_t>(oldSpaceAllocLimit * IDLE_SPACE_SIZE_LIMIT_RATE);
126 if (heap_->GetHeapObjectSize() > idleOldSpaceAllocLimit) {
127 return true;
128 }
129 return false;
130 }
131
ReachIdleSharedGCThresholds()132 bool IdleGCTrigger::ReachIdleSharedGCThresholds()
133 {
134 size_t expectSizeLimit = sHeap_->GetOldSpace()->GetInitialCapacity() * IDLE_SPACE_SIZE_LIMIT_RATE;
135 size_t currentOldSize = sHeap_->GetOldSpace()->GetHeapObjectSize();
136 size_t expectGlobalSizeLimit = sHeap_->GetGlobalSpaceAllocLimit() * IDLE_SPACE_SIZE_LIMIT_RATE;
137 return currentOldSize > expectSizeLimit || sHeap_->GetHeapObjectSize() > expectGlobalSizeLimit;
138 }
139
TryPostHandleMarkFinished()140 void IdleGCTrigger::TryPostHandleMarkFinished()
141 {
142 if (IsIdleState()) {
143 PostIdleGCTask(TRIGGER_IDLE_GC_TYPE::LOCAL_REMARK);
144 }
145 }
146
PostIdleGCTask(TRIGGER_IDLE_GC_TYPE gcType)147 void IdleGCTrigger::PostIdleGCTask(TRIGGER_IDLE_GC_TYPE gcType)
148 {
149 if (triggerGCTaskCallback_ != nullptr && IsPossiblePostGCTask(gcType) && !heap_->NeedStopCollection()) {
150 std::pair<void*, uint8_t> data(heap_->GetEcmaVM(), static_cast<uint8_t>(gcType));
151 triggerGCTaskCallback_(data);
152 SetPostGCTask(gcType);
153 LOG_GC(INFO) << "IdleGCTrigger: post once " << GetGCTypeName(gcType) << " on idleTime";
154 }
155 LOG_GC(DEBUG) << "IdleGCTrigger: failed to post once " << GetGCTypeName(gcType);
156 }
157
158 template<class T>
ShouldCheckIdleOldGC(const T * baseHeap) const159 bool IdleGCTrigger::ShouldCheckIdleOldGC(const T *baseHeap) const
160 {
161 size_t heapAliveSizeAfterGC = baseHeap->GetHeapAliveSizeAfterGC();
162 if (heapAliveSizeAfterGC == 0) {
163 return false;
164 }
165 size_t expectHeapSize = std::max(static_cast<size_t>(heapAliveSizeAfterGC * IDLE_SPACE_SIZE_MIN_INC_RATIO),
166 heapAliveSizeAfterGC + IDLE_SPACE_SIZE_MIN_INC_STEP);
167 LOG_ECMA_IF(optionalLogEnabled_, DEBUG) << "IdleGCTrigger: check old GC heapAliveSizeAfterGC:"
168 << heapAliveSizeAfterGC << ";expectHeapSize" << expectHeapSize
169 << " heapObjectSize" << baseHeap->GetHeapObjectSize();
170 return baseHeap->GetHeapObjectSize() >= expectHeapSize;
171 }
172
173 template<class T>
ShouldCheckIdleFullGC(const T * baseHeap) const174 bool IdleGCTrigger::ShouldCheckIdleFullGC(const T *baseHeap) const
175 {
176 size_t heapAliveSizeAfterGC = baseHeap->GetHeapAliveSizeAfterGC();
177 size_t expectHeapSize = std::max(static_cast<size_t>(heapAliveSizeAfterGC * IDLE_SPACE_SIZE_MIN_INC_RATIO),
178 heapAliveSizeAfterGC + IDLE_SPACE_SIZE_MIN_INC_STEP_FULL);
179 LOG_GC(DEBUG) << "IdleGCTrigger: check full GC heapAliveSizeAfterGC:" << heapAliveSizeAfterGC
180 << ";expectHeapSize:" << expectHeapSize << ";heapObjectSize:" << baseHeap->GetHeapObjectSize();
181 if (baseHeap->GetHeapObjectSize() >= expectHeapSize) {
182 return true;
183 }
184 size_t fragmentSizeAfterGC = baseHeap->GetFragmentSizeAfterGC();
185 size_t heapBasicLoss = baseHeap->GetHeapBasicLoss();
186 if (fragmentSizeAfterGC <= heapBasicLoss) {
187 return false;
188 }
189 size_t fragmentSize = fragmentSizeAfterGC - heapBasicLoss;
190 size_t expectFragmentSize = std::max(static_cast<size_t>((baseHeap->GetCommittedSize() - heapBasicLoss) *
191 IDLE_FRAGMENT_SIZE_RATIO), IDLE_MIN_EXPECT_RECLAIM_SIZE);
192 LOG_GC(DEBUG) << "IdleGCTrigger: check full GC fragmentSizeAfterGC:" << fragmentSizeAfterGC
193 << ";heapBasicLoss:" << heapBasicLoss << ";expectFragmentSize" << expectFragmentSize;
194 return fragmentSize >= expectFragmentSize;
195 }
196
CheckIdleYoungGC() const197 bool IdleGCTrigger::CheckIdleYoungGC() const
198 {
199 auto newSpace = heap_->GetNewSpace();
200 LOG_GC(DEBUG) << "IdleGCTrigger: check young GC semi Space size since gc:"
201 << newSpace->GetAllocatedSizeSinceGC(newSpace->GetTop());
202 return newSpace->GetAllocatedSizeSinceGC(newSpace->GetTop()) > IDLE_MIN_EXPECT_RECLAIM_SIZE;
203 }
204
TryTriggerIdleGC(TRIGGER_IDLE_GC_TYPE gcType)205 void IdleGCTrigger::TryTriggerIdleGC(TRIGGER_IDLE_GC_TYPE gcType)
206 {
207 LOG_GC(DEBUG) << "IdleGCTrigger: recv once notify of " << GetGCTypeName(gcType);
208 switch (gcType) {
209 case TRIGGER_IDLE_GC_TYPE::OLD_GC:
210 if (ShouldCheckIdleOldGC<Heap>(heap_) && !heap_->NeedStopCollection()) {
211 LOG_GC(INFO) << "IdleGCTrigger: trigger " << GetGCTypeName(gcType);
212 heap_->CollectGarbage(TriggerGCType::OLD_GC, GCReason::IDLE);
213 }
214 break;
215 case TRIGGER_IDLE_GC_TYPE::FULL_GC:
216 if (ShouldCheckIdleFullGC<Heap>(heap_) && !heap_->NeedStopCollection()) {
217 LOG_GC(INFO) << "IdleGCTrigger: trigger " << GetGCTypeName(gcType);
218 heap_->CollectGarbage(TriggerGCType::FULL_GC, GCReason::IDLE);
219 } else if (CheckIdleYoungGC() && !heap_->NeedStopCollection()) {
220 LOG_GC(INFO) << "IdleGCTrigger: trigger young gc";
221 heap_->CollectGarbage(TriggerGCType::YOUNG_GC, GCReason::IDLE);
222 }
223 break;
224 case TRIGGER_IDLE_GC_TYPE::SHARED_GC:
225 if (ShouldCheckIdleOldGC<SharedHeap>(sHeap_) && !heap_->NeedStopCollection()) {
226 LOG_GC(INFO) << "IdleGCTrigger: trigger " << GetGCTypeName(gcType);
227 sHeap_->CollectGarbage<TriggerGCType::SHARED_GC, GCReason::IDLE>(thread_);
228 }
229 break;
230 case TRIGGER_IDLE_GC_TYPE::SHARED_FULL_GC:
231 if (ShouldCheckIdleFullGC<SharedHeap>(sHeap_) && !heap_->NeedStopCollection()) {
232 LOG_GC(INFO) << "IdleGCTrigger: trigger " << GetGCTypeName(gcType);
233 sHeap_->CollectGarbage<TriggerGCType::SHARED_FULL_GC, GCReason::IDLE>(thread_);
234 }
235 break;
236 case TRIGGER_IDLE_GC_TYPE::LOCAL_CONCURRENT_MARK:
237 if (ShouldCheckIdleOldGC<Heap>(heap_) && !heap_->NeedStopCollection()) {
238 LOG_GC(INFO) << "IdleGCTrigger: trigger " << GetGCTypeName(gcType);
239 TryTriggerLocalConcurrentMark();
240 }
241 break;
242 case TRIGGER_IDLE_GC_TYPE::LOCAL_REMARK:
243 if (!heap_->NeedStopCollection()) {
244 TryTriggerHandleMarkFinished();
245 LOG_GC(INFO) << "IdleGCTrigger: trigger " << GetGCTypeName(gcType);
246 }
247 break;
248 default:
249 LOG_GC(ERROR) << "IdleGCTrigger: this branch is unreachable";
250 return;
251 }
252 ClearPostGCTask(gcType);
253 }
254 }