• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }