作者:林冠宏 / 指尖下的幽灵
GitHub : https://github.com/af913337456/
腾讯云专栏: https://cloud.tencent.com/developer/user/1148436/activities
回答内存管理类面试问题可以说出下面这些内容,加分。
前言: 站在巨人的肩膀上,总结此文。
目录:
- Java runtime 三个计算内存函数
- OOM 的说法,为什么大型游戏能申请那么多内存?
- 如何绕过dalvikvm heap size的限制 ?
- Bitmap分配在native heap还是dalvik heap上?
1,Java runtime 三个计算内存函数:
maxMemory
获取当前 APP 最大能够申请的内存,在 Java Heap 部分。
totalMemory
获取当前 APP 已经从系统拿到的内存,包含使用上了的和没有用上的,因为一般申请会申请多一部分,它总是慢慢按需要从系统拿取。
freeMemory
获取当前 APP 拿到的内存中,还没用上的,即是可以被 gc 回收的。
计算此刻 APP 在 Java Heap 层次已经使用了的内存 usedMemory
:
usedMemory = totalMemory - freeMemory
2,OOM 的说法,为什么大型游戏能申请那么多内存?
在不同的 android 系统版本中,OOM 的判断是不一样的。
-
通俗来说,OOM 是当前进程共申请的内存综和超过一个限制,而被抛出。
-
专业来说,Android为每个进程设置Dalvik Heap Size阈值,这个阈值在不同的设备上会因为RAM大小不同而各有差异。如果APP想要分配的内存超过这个阈值,就会发生OOM。
-
Android
3.x以前
,Bitmap分配在Native heap中,而在3.x之后
,Bitmap分配在Dalvik或ART的Java heap中。 -
Android 2.x系统,当dalvik allocated + native allocated + 新分配的大小 >= dalvik heap 最大值时候就会发生OOM,也就是说在2.x系统中,考虑native heap对每个进程的内存限制。
-
Android 3.x系统,废除了native的计数器,类似bitmap的分配改到dalvik的java heap中申请,只要allocated + 新分配的内存 >= dalvik heap 最大值的时候就会发生OOM(art运行环境的统计规则还是和dalvik保持一致),也就是说在3.x系统中,不考虑native heap对每个进程的内存限制,native heap只会收到本机总内存(包括RAM以及SWAP区或分页文件)的限制。
这也是为什么有些APP(比如大型游戏)可以超过 Dalvik Heap Size 这个值?那是因为Java内存又分为Java Heap和Native Heap,3.X 后 Native Heap是不受该值约束的。像C/C++的内存都是在Native Heap中分配的。另外Bitmap是在Java Heap中分配的,我们开发过程中经常遇到由Bitmap引起的OOM,这就是一个例子。
3,如何绕过dalvikvm heap size的限制 ?
-
创建子进程,上面说了,内存分配按进程来。再使用进程通讯
创建一个新的进程,那么我们就可以把一些对象分配到新进程的heap上了,从而达到一个应用程序使用更多的内存的目的,当然,创建子进程会增加系统开销,而且并不是所有应用程序都适合这样做,视需求而定。
创建子进程的方法:使用android:process标签
-
按不同的系统版本,使用 jni 在native heap上申请空间(推荐使用)
3.X 后的系统 native heap的增长并不受dalvik vm heapsize的限制,只要RAM有剩余空间,程序员可以一直在native heap上申请空间,当然如果 RAM快耗尽,memory killer会杀进程释放RAM。大家使用一些软件时,有时候会闪退,就可能是软件在native层申请了比较多的内存导致的。比如,我就碰到过UC web在浏览内容比较多的网页时闪退,原因就是其native heap增长到比较大的值,占用了大量的RAM,被memory killer杀掉了。
-
使用显存(操作系统预留RAM的一部分作为显存)
使用OpenGL textures等API,texture memory不受dalvik vm heapsize限制,这个我没有实践过。再比如Android中的GraphicBufferAllocator申请的内存就是显存。
4,Bitmap分配在native heap还是dalvik heap上?
上面说了,不同的系统版本不同,那么在 3.X 及其之后,为什么在 java heap 而不是在 native heap 。请看下面源码。
主要的文件
framework/base/graphic/java/Android/graphics/BitmapFactory.java
framework/base/core/jni/Android/graphics/BitmapFactory.cpp
framework/base/core/jni/Android/graphics/Graphics.cpp
BitmapFactory.java 里面有几个decode***方法用来创建bitmap,最终都会调用:
private staticnative Bitmap nativeDecodeStream(InputStream is, byte[] storage,Rect padding,Options opts);
而nativeDecodeStream()
会调用到BitmapFactory.cpp
中的deDecode
方法,最终会调用到Graphics.cpp
的createBitmap
方法。
createBitmap方法的实现:
jobjectGraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
boolisMutable, jbyteArray ninepatch, int density)
{
SkASSERT(bitmap);
SkASSERT(bitmap->pixelRef());
jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
static_cast<jint>(reinterpret_cast<uintptr_t>(bitmap)),
buffer, isMutable, ninepatch,density);
hasException(env); // For the side effectof logging.
return obj;
}
从代码中可以看到bitmap
对象是通过env->NewOject(...)
创建的,到这里疑惑就解开了,bitmap对象是虚拟机创建的,JNIEnv的NewOject方法返回的是java对象,并不是native对象,所以它会分配到dalvik heap
中。