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
Listaccs = [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.