本篇参考:
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的优化相辅相成,不要为了某一个优化而特意放弃另外一个,彼此形成一下平衡。篇中有错误地方欢迎指出,有不懂欢迎留言。