Friday, February 10, 2012

Salesforce LimitException: Apex heap size too large

Monitoring the Heap Size

From the log files you can monitor heap memory allocation:

09:43:02:056 HEAP_ALLOCATE [62]|Bytes:5

Or get the Maximum heap size from the LIMIT_USAGE_FOR_NS log entry.

LIMIT_USAGE_FOR_NS from a sandbox:

12:18:50.117|LIMIT_USAGE_FOR_NS|(default)|
  Number of SOQL queries: 5 out of 100
  Number of query rows: 9 out of 50000
  Number of SOSL queries: 0 out of 20
  Number of DML statements: 1 out of 150
  Number of DML rows: 1 out of 10000
  Number of script statements: 212 out of 200000
  Maximum heap size: 0 out of 6000000
  Number of callouts: 3 out of 10
  Number of Email Invocations: 0 out of 10
  Number of fields describes: 0 out of 100
  Number of record type describes: 0 out of 100
  Number of child relationships describes: 0 out of 100
  Number of picklist describes: 0 out of 100
  Number of future calls: 0 out of 10

LIMIT_USAGE_FOR_NS from production:

11:36:46.701|LIMIT_USAGE_FOR_NS|(default)|
  Number of SOQL queries: 22 out of 100
  Number of query rows: 68 out of 50000
  Number of SOSL queries: 0 out of 20
  Number of DML statements: 0 out of 150
  Number of DML rows: 0 out of 10000
  Number of script statements: 543 out of 200000
  Maximum heap size: 5744310 out of 6000000 ******* CLOSE TO LIMIT
  Number of callouts: 3 out of 10
  Number of Email Invocations: 0 out of 10
  Number of fields describes: 0 out of 100
  Number of record type describes: 0 out of 100
  Number of child relationships describes: 0 out of 100
  Number of picklist describes: 0 out of 100
  Number of future calls: 0 out of 10

Note how the the sandbox doesn't seem to be tracking the heap size.

Use Limits to check the values in Apex:

System.debug(LoggingLevel.Debug, 'Heap Size: ' + Limits.getHeapSize() + '/' + Limits.getLimitHeapSize());
09:43:04:672 USER_DEBUG [155]|DEBUG| Heap Size: 8877586/6000000

Note how the actual Heap Size reported here is greater than the Heap Limit size! This seems to be an oddity of the sandbox environment as in production a LimitException would occur.

Use SOQL For Loops rather than standard SQOL queries

From the documentation on SOQL For Loops:

Developers should always use a SOQL for loop to process query results that return many records, to avoid the limit on heap size.

So rather than

List accs = [SELECT Id, Name FROM Account];  
for(Account a : accs){
   System.debug(a.Name);
}

You would have:

for(Account a : [SELECT Id, Name FROM Account]){
   System.debug(a.Name);
}

Use a Batch Apex or a future method

I recently had a task to upload the binary/Blob for every attachment on an opportunity to a web service as a base64 encoded string. Even after switching to a SOQL for loop it was possible to reach the standard heap size of 6000000 bytes. The attachments could be up to 5 MB in size, so just by loading the attachment Blob out of the database for one record there wasn't much space left over to base64 encode it using EncodingUtil.base64Encode.

Switching to Batch Apex increased the heap limit to 12000000 bytes. Also, by setting the scope to 1 only one attachment was processed at a time.