本篇參考:
https://help.salesforce.com/s/articleView?id=000384468&type=1
https://help.salesforce.com/s/articleView?id=000385712&type=1
此前講過CPU limitation:salesforce零基礎學習(一百零二)Limitation篇之 CPU Limit
本篇說一下項目中也經常用到的 heap size limitation以及best practice.
首先先說一下 salesforce中的 heap size簡單概念, salesforce中的heap size和java中的heap size概念基本相同,當對象或者變量創建時,就會給分配內存,當運行時基於邏輯動態分配內存。salesforce限制同步最大的 apex heap size是6MB,異步的場景最多的是12MB. 當transaction執行時,太多數據存儲在內存中的情況下,可能觸發 The "Apex heap size too large" 的錯誤。
如果去調查某一個功能邏輯的heap size情況,可以通過以下的步驟來分析:
- 通過debug log查看當前的 heap size情況。
- 在debug log中通過HEAP_ALLOCATE來確定對象或者變量的分配內存的情況。
- 通過最後的Maximum heap size: 瞭解當前的執行的transaction所使用的heap size情況。
- 針對heap size limit擁有兩個方法可以查詢:
- Limits.getHeapSize():返回已用於堆的大致內存量(單位爲:字節)。
- Limits.getLimitHeapSize(): 返回堆中還可以使用的大致的內存量(單位爲:字節)
Best practice
1. 不使用class級別的變量去存儲大量數據(也不一定侷限於 class級別的變量,list儘量別存儲大量數據)
錯誤案例: 下面的demo中: baseList,SampleMap的value以及tempt list都指向了同一個內存地址,執行以後,這個內存地址便會超限,從而觸發The "Apex heap size too large" 的limitation
String tStr = 'aaaaa bbbbb ccccc ddddd eeeeee fffff ggggg 11111 22222 33333 44444'; List<String> baseList = tStr.split(' '); List<String> bigList = baseList; Map<integer, List<String>> SampleMap = new Map<integer, List<String>>(); SampleMap.put(1, bigList); for (integer i=0; i<50; i++) { List<String> tempList = new List<String>(); tempList = SampleMap.get(1); bigList.addAll(tempList); } system.debug('FINAL LIST SIZE IS '+bigList.size());
簡單的改動就是聲明一個新的 list,避免之前的內存倍速增長。
String tStr = 'aaaaa bbbbb ccccc ddddd eeeeee fffff ggggg 11111 22222 33333 44444'; List<String> baseList = tStr.split(' '); Map<integer, List<String>> Sample = new Map<integer, List<String>>(); List<String> bigList = baseList; Sample.put(1, bigList); List<string> myList = new list<string>(); //Declare a new list for (integer i=0; i<50; i++) { List<String> tempList = new List<String>(); tempList = Sample.get(1); system.debug('templist: ' + tempList.size()); system.debug(' bigList: ' + bigList.size()); myList.addall(tempList); //original code is bigList.addall(tempList); } system.debug('FINAL LIST SIZE OF bigList IS '+ bigList.size()); system.debug('myList IS '+mylist.size());
2. 使用SOQL for loop從大量查詢的數據中迭代和處理數據,官方也介紹了很多的 SOQL for loop的demo。詳情可查看:
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_loops_for_SOQL.htm
未改進前的簡單的demo如下:
List<Account> accs = [SELECT Id, Name FROM Account LIMIT 50000];
針對 heap size修改的情況下,官方給出的建議,此種情況下,每200條執行一次數據,大量的減少了 heap size的使用。
SOQL for loop通過調用SOAP API的query和queryMore方法,使用高效的分塊來檢索所有sObjects(每次處理200條數據)。開發人員可以通過使用SOQL for loop處理返回多條記錄的查詢結果來避免堆大小的限制。
for(Account a : [SELECT Id, Name FROM Account LIMIT 50000]){ //TODO custom logic }
當然,在多租戶環境下,我們的limitation也不止 heap size,上述方法是否是最優解需要具體情況具體分析。當我們使用 SOQL for loop並且數據量大的情況下,這種方法可能會導致使用更多的CPU週期,邏輯執行時間也變得多了。除了官方上面的鏈接介紹以外,也可以看一下下面的邱老闆的demo
https://blog.keal.us/salesforce/soql-for-loop%e7%9a%84%e6%95%88%e7%8e%87%e9%97%ae%e9%a2%98/
3. 變量使用 'transient'關鍵字,用於聲明不需要被保存的變量,並且在VF page情況下也不會計入view state
4. 在運行時環境下,通過在迭代list / set / map時從集合中移除不必要的item來減小堆大小。
除此以外的幾點優化點作爲參考:
1. 避免使用無效的臨時變量。比如代碼中的臨時變量後續沒有調用,造成了額外的花銷,這種沒有用的代碼儘量刪除。
2. 更短的命名以及Field api 名稱: 誠然使用好的命名規範有更強的可讀性,不過短的名字確實可以省一些 heap size
移除不必要的debug log語句,特別是生產環境。
總結:heap size和CPU limitation的優化相輔相成,不要爲了某一個優化而特意放棄另外一個,彼此形成一下平衡。篇中有錯誤地方歡迎指出,有不懂歡迎留言。