In an ObjC framework I'm developing (a dylib) that is loaded into JRE to be used via JNI (Zulu, Graal, or "native image" from Graal+ a JAR) I implemented a naive method that collects current memory footprint of the host process: It collects 5 numbers into a simple NSDictionary with NSString keys (physical footprint, default zone bytes used and allocated, and sums for used and allocated bytes for all zones.
The code ran for some time, but at certain point my process started crashing horribly in this method -- at the last line, accessing the dictionary.
Here's the code:
-(NSDictionary *)memoryState {
NSMutableDictionary *memoryState = [NSMutableDictionary dictionaryWithCapacity:8];
// obtain process current physical memory footprint, in bytes.
task_vm_info_data_t info;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
kern_return_t kr = task_info(mach_task_self(),
TASK_VM_INFO, (task_info_t)&info, &count);
[memoryState setObject:(kr == KERN_SUCCESS) ? @(info.phys_footprint) : [NSNull null] forKey:@"physical"];
// obtain process default zone's allocated memory, in bytes.
malloc_zone_t *zone = malloc_default_zone();
if (zone!=nil) {
malloc_statistics_t st;
malloc_zone_statistics(zone, &st);
[memoryState setObject:@(st.size_in_use) forKey:@"bytesInUseDefaultZone"];
[memoryState setObject:@(st.size_allocated) forKey:@"bytesAllocatedDefaultZone"];
}
uint64_t zone_count = 0, size_in_use =0, size_allocated = 0;
vm_address_t *zones = NULL;
unsigned int zones_count = 0;
kr = malloc_get_all_zones(mach_task_self(), NULL, &zones, &zones_count);
if (kr == KERN_SUCCESS && zones != NULL && zones_count > 0) {
for (unsigned int i = 0; i < zones_count; i++) {
malloc_zone_t *zone = (malloc_zone_t *)zones[i];
if (!zone) continue;
malloc_statistics_t st;
malloc_zone_statistics(zone, &st);
zone_count++;
size_in_use += (uint64_t)st.size_in_use;
size_allocated += (uint64_t)st.size_allocated;
}
[memoryState setObject:@(size_in_use) forKey:@"bytesInUseAllZones"];
[memoryState setObject:@(size_allocated) forKey:@"bytesAllocatedAllZones"];
}
if (zones != NULL) {
vm_deallocate(mach_task_self(), (vm_address_t)zones, zones_count * sizeof(vm_address_t));
}
return [memoryState copy];
}
my (JRE) process started crashing badly, at the last [memoryState copy]; with crash report I could not understand (looks like an infinite recursion or loop).
Any debug log messages (os_log) for this memoryState, its items or its copy would crash the same.
Finally I found that commenting out the vm_deallocate() call removes the crash.
Sorry to say - I could NOT find anywhere in the documentation anything about malloc_get_all_zones() returned data, and whether I need to deallocate it after use. Some darn AI analyzer pointed out I "had a leak" and that "Apple documentation" which it didn't provide, requires that I thus release this data.
1 ) Do I really have to deallocate the returned "zones" ?? even if I do, something here is strange - zones is a malloc_zone_t ** -- how can it be casted to (vm_address_t)zones
- Where can I read actual documentation about these low level APIs and the correct use?
Thanks!
The documentation for our low-level APIs is quite scattered, something I going into detail in in Availability of Low-Level APIs. In this specific case a good start is the header itself, which has this key titbits:
Note that the validity of the addresses returned correspond to the validity reader
You’re passing in NULL, which gives you the in-memory reader, which returns everything directly, and thus doesn’t need to be deallocated.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"