应用程序是无法直接操作物理内存的,也无法感知到物理内存,应用程序面向的内存只有虚拟内存,所以我们在程序开发的过程中,分配或申请的内存实际上都是虚拟内存。那么要如何申请虚拟内存呢?其中又经历了哪些流程呢?我们接着往下看。
在进行Android应用开发时,如果我们是做Native开发的,就需要手动申请或释放内存。而如果只是写Java层的代码,那么不需要我们自己去申请内存,在创建对象和声明变量、常量等操作时,虚拟机会自动为这些数据申请内存。并且在使用完毕后,我们也不需要自己去做内存释放,虚拟机会自动释放这些内存。虚拟机申请和释放内存的方式,和我们做Native开发时申请和释放内存的方式是一样的,都是使用malloc函数在堆空间上为数据申请合适的内存,并在数据使用结束后使用free函数释放内存。
1.malloc函数
我们先看看malloc函数,该函数很简单,调用时我们只需要传入要申请的内存大小即可。如果分配成功,函数会返回void*指针地址,失败则返回NULL。
malloc函数是一个C语言库的函数,所以它分配内存最终还是得调用Linux系统提供的函数,让Linux内核去帮我们申请内存。而内核会根据申请的内存大小来执行不同的申请策略,主要有以下两种策略。
1)如果申请的内存小于或等于128KB,则内核会调用brk()函数来申请内存。sbrk()会将堆顶指针向高地址移动,获得新的虚拟内存空间,这种方式在申请和释放内存时会更加简单高效。
2)如果申请的内存大于128KB,则内核会调用mmap()函数,在堆中分配我们所需大小的内存空间。在申请内存时,这种方式可以对较大的内存进行内存对齐,提高访问效率。
2.mmap函数
mmap函数是一个很重要的函数,后面会反复用到,所以我们在这里对这个函数进行一定的讲解。mmap函数有两种用法:第一种是将一个文件映射到进程的虚拟内存中,进程可以通过内存访问的方式来读写对象;第二种是不映射文件,而是直接在虚拟内存中申请一块空的内存空间。mmap函数如下:
mmap函数的每个入参解释如下:
❑参数addr指向欲映射的内存起始地址,通常设为NULL,代表让系统自动选定地址,并在映射成功后返回该地址。
❑参数length表示映射到内存中的数据大小。
❑参数prot指定映射区域的读写权限。
❑参数flags指定映射时的特性,如是否允许其他进程映射这段内存等。
❑参数fd指定映射进内存的文件的描述符。
❑参数offset指定映射位置的偏移量,一般为0。
对于入参fd,我们可以传入想要映射进用户空间的文件地址,也可以不映射文件,这两种用法的解释如下:
1)如果想要映射磁盘文件到用户空间中,fd会传入我们要映射的文件。这种用法可以让我们读写文件的效率更高,可以用来实现数据的跨进程传输,比如Android共享内存机制、Binder通信都是通过mmap文件映射来实现的。
2)入参fd置为-1,表示不映射磁盘文件,而是在堆空间中申请一块内存。虚拟机的malloc函数使用的就是这种用法,它会直接在Java堆空间中申请一块内存。malloc函数申请的内存是虚拟内存,并且不会分配和映射真正的物理内存,只有当我们真正要往这块虚拟内存区域中写入数据,操作系统检查到对应的虚拟内存没有映射到物理内存而发生缺页中断时,才会分配一块同样大小的物理内存,并建立映射关系。这是一种懒加载技术,可以提升内存的使用效率。
3.free函数
内存的释放则是调用free函数,我们只需要传入要释放的首地址,这个地址是调用malloc函数后返回的地址。我们不需要传入要释放的内存大小,因为内存管理机制已经记录了这个地址分配的内存大小信息。当申请的内存不再使用时,一定要记得调用free函数释放掉这部分内存,不然会发生内存泄漏。