当时跟hippy SDK的同事也讨论过是否存在类似的内存不足情况 。但由于大家对JSC黑盒都不熟悉 , 而且崩溃的JS堆栈也不确切 。当时的建议是:少在后台加载JSC 。最终也并没有解决该问题 。
两年后 , 当浏览器集成flutter , 类似的JS崩溃直接翻倍(21H2 0.08% -> 22H1 0.16%) 。没办法 , 还是要看类似JSC和Dart VM的内存分配机制是怎样的 , 再挖掘一下是否存在解(缓)决(解)方案 。
JSC、DartVM的虚拟内存分配翻阅相关虚拟机的内存管理相关代码 , 可以找到底层的内存分配基本实现都是基于mmap处理的 。
// WebKit bmalloc VMAllocateinline void* tryVMAllocate(size_t vmSize, VMTag usage = VMTag::Malloc){vmValidate(vmSize);void* result = mmap(0, vmSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | BMALLOC_NORESERVE, static_cast<int>(usage), 0);if (result == MAP_FAILED)return nullptr;return result;}
// Dart VM的虚拟内存VirtualMemory* VirtualMemory::Allocate(intptr_t size,bool is_executable,const char* name) {ASSERT(Utils::IsAligned(size, PageSize()));const int prot = PROT_READ | PROT_WRITE | (is_executable ? PROT_EXEC : 0);int map_flags = MAP_PRIVATE | MAP_ANONYMOUS;#if (defined(DART_HOST_OS_macOS) && !defined(DART_HOST_OS_IOS))if (is_executable && IsAtLeastOS10_14()) {map_flags |= MAP_JIT;}#endif// defined(DART_HOST_OS_MACOS)// Some 64-bit microarchitectures store only the low 32-bits of targets as// part of indirect branch prediction, predicting that the target's upper bits// will be same as the call instruction's address. This leads to misprediction// for indirect calls crossing a 4GB boundary. We ask mmap to place our// generated code near the VM binary to avoid this.void* hint = is_executable ? reinterpret_cast<void*>(&Allocate) : nullptr;void* address = mmap(hint, size, prot, map_flags, -1, 0);if (address == MAP_FAILED) {return nullptr;}return new VirtualMemory(address, size);}VirtualMemory::~VirtualMemory() {if (address_ != nullptr) {if (munmap(address_, size_) != 0) {int error = errno;const int kBufferSize = 1024;char error_buf[kBufferSize];FATAL("munmap error: %d (%s)", error,Utils::StrError(error, error_buf, kBufferSize));}}}
当map_flags包含MAP_ANON时 , 并且fd传入-1时 , mmap将直接使用虚拟内存进行分配 , 不需要依赖文件描述符 。
mmap在xnu上的实现/* * mmap stub, with preemptory failures due to extra parameter checking * mandated for conformance. * * This is for UNIX03 only. */void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off){/** Preemptory failures:** ooff is not a multiple of the page size* oflags does not contain either MAP_PRIVATE or MAP_SHARED* olen is zero*/extern void cerror_nocancel(int);if ((off & PAGE_MASK) ||(((flags & MAP_PRIVATE) != MAP_PRIVATE) &&((flags & MAP_SHARED) != MAP_SHARED)) ||(len == 0)) {cerror_nocancel(EINVAL);return(MAP_FAILED);}void *ptr = __mmap(addr, len, prot, flags, fildes, off);if (__syscall_logger) {int stackLoggingFlags = stack_logging_type_vm_allocate;if (flags & MAP_ANON) {stackLoggingFlags |= (fildes & VM_FLAGS_ALIAS_MASK);} else {stackLoggingFlags |= stack_logging_type_mapped_file_or_shared_mem;}__syscall_logger(stackLoggingFlags, (uintptr_t)mach_task_self(), (uintptr_t)len, 0, (uintptr_t)ptr, 0);}return ptr;}
上面的调用会传递到内核kern_mman.c的实现函数mmap(proc_t p, struct mmap_args *uap, user_addr_t *retval)
/* * XXX Internally, we use VM_PROT_* somewhat interchangeably, but the correct * XXX usage is PROT_* from an interface perspective.Thus the values of * XXX VM_PROT_* and PROT_* need to correspond. */intmmap(proc_t p, struct mmap_args *uap, user_addr_t *retval){/** 上面忽略了一部分代码*/result = vm_map_enter_mem_object(user_map,&user_addr, user_size,0, alloc_flags, vmk_flags,tag,IPC_PORT_NULL, 0, FALSE,prot, maxprot,(flags & MAP_SHARED) ?VM_INHERIT_SHARE :VM_INHERIT_DEFAULT);/* If a non-binding address was specified for this anonymous* mapping, retry the mapping with a zero base* in the event the mapping operation failed due to* lack of space between the address and the map's maximum.*/if ((result == KERN_NO_SPACE) && ((flags & MAP_FIXED) == 0) && user_addr && (num_retries++ == 0)) {user_addr = vm_map_page_size(user_map);goto map_anon_retry;}/** 下面忽略了一部分代码*/}
其中又会调用vm_map.c内部的vm_map_enter_mem_object , 而该方法最终会在vm_map_enter中依据对象进行内存分配:
// 下面这个只截了个头 , 大概带一下 , 我也没调过代码~/* *Routine:vm_map_enter * *Description: *Allocate a range in the specified virtual address map. *The resulting range will refer to memory defined by *the given memory object and offset into that object. * *Arguments are as defined in the vm_map call. */kern_return_tvm_map_enter(vm_map_tmap,vm_map_offset_t*address,/* IN/OUT */vm_map_size_tsize,vm_map_offset_tmask,intflags,vm_map_kernel_flags_tvmk_flags,vm_tag_talias,vm_object_tobject,vm_object_offset_toffset,boolean_tneeds_copy,vm_prot_tcur_protection,vm_prot_tmax_protection,vm_inherit_tinheritance)
推荐阅读
- 苹果这一限制终于解除,iOS 更开放
- 能让 iOS 保持流畅的墓碑机制,安卓也有了
- 苹果|苹果发布iOS/iPad OS 15.6准正式版:修复Bug 流畅度/性能继续提升
- 苹果13.6.1系统怎么样?
- 什么叫生物圈?
- 《dnf》一键拾取怎么设置?
- 快手怎么一键取消关注?
- ios14画中画适配app了吗?
- 卸载手机应用,很多人第一步就错了,教你正确方法,释放大量内存
- 苹果路由器来了,搭载 iOS 系统