概述
前言
在日常的Android开发中,每个开发者或多或少都会遇到过OutOfMemoryError这样崩溃信息。如果工程稍微大一些,在monkey测试的崩溃日志也是比较常见的一种。如下是比较常见的一些报错信息:
Android:java.lang.OutOfMemoryError: Failed to allocate a 1340012 byte allocation with 72503 free bytes and 70KB until OOM
OutOfMemoryError: (Heap Size=49187KB, Allocated=41957KB)
COMPILETODALVIK : UNEXPECTED TOP-LEVEL error :
java.lang.OutOfMemoryError: Java heap space
...
java.lang.StackOverflowError
...
相对于StackOverflowError而言OutOfMemoryError则是比较常见的一种内存异常。在《深入理解Java虚拟机》一书中有对这两种异常信息的简单描述。
如果线程请求的栈深度大于虚拟机所允许的深度,将会抛出StackOverflowError;如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可以动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError。
StackOverflowError不常见,主要在渲染复杂布局或者动态缓存数据到数据库时比较常见。而OutOfMemoryError只要内存泄漏多了就有可能导致内存溢出。
内存泄漏与内存溢出
内存泄漏 Memeory Leak程序在向系统申请分配内存空间后(new),在使用完毕后未释放。结果导致一直占据该内存单元,我们和程序都无法再使用该内存单元,直到程序结束,这是内存泄露。
内存溢出 Out Of Memory是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如内存只能分配一个int类型,我却要塞给他一个long类型,系统就出现oom。又比如一车最多能坐5个人,你却非要塞下10个,车就挤爆了。
内存泄漏的堆积最终会导致内存溢出。从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。
为什么会产生内存泄漏
为了判断Java中是否有内存泄漏,首先我们必须了解Java是如何管理内存的。Java的内存管理可以简单理解为对象的分配和释放。在Java中内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)自动完成的。程序员不需要自己调用方法释放内存,但它只能回收无用且不被其它对象引用的那些对象占用的空间。
Java内存回收机制就是从程序的GC root(静态对象以及堆内存对象等)开始检查引用链,当遍历一遍之后得到上述无法回收的对象以及它们所引用的对象链,组成无法回收的对象集合,而其它孤立的对象就作为垃圾回收。GC为了能够正确释放对象,必须监控每一个对象的状态,包括对象的申请、引用、被引用、赋值等,GC都需要对其进行监控。监控对象的状态是为了更加准确地、及时地释放对象,而释放对象的基本原则就是该对象不再被使用。
在Java中上述的那些无用的对象由GC负责回收,因此程序员不需要考虑这部分内存泄漏。虽然我们有几个方法可以方位GC,例如System.gc(),但是根据Java语言规范的定义,该方法并不会保证Java的垃圾收集器一定会执行。因为不同的JVM实现者可能使用的是不同的算法实现的GC。通常GC线程的优先级比较低。JVM调用GC的策略也有许多中,有的是当内存使用的一定限度时才会执行GC,也有定时执行的。再者Java编程规范也不建议开发人员自己手动调用System.gc()。
Android应用程序采用Java编程语言编写,根据上面描述,Java区别于其他语言的一个重要优点就是它通过GC 自动管理内存的回收,Android程序员只需通过内存分配操作创建对象,而无须关心对象占用的空间是如何被收回的。因此很多程序员认为在Java中不必担心内存泄漏的问题,然而实际并非如此,Java中仍然存在着内存泄漏。Android应用程序一般都是运行在移动设备中的,而移动设备中内存的总量非常有限,因此如何合理地规避“内存泄露”问题也就显得十分关键。
Android中内存介绍
在Android中获取系统分配内存的大小一般采用如下两种方式:
ActivityManager mgr= (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
int memSize=mgr.getMemoryClass();
int maxSize=mgr.getLargeMemoryClass();
builder.append("系统分配内存大小:"+memSize+"Mn");
builder.append("系统分配最大内存:"+maxSize+"Mnn");
long totalMem=Runtime.getRuntime().totalMemory()/SIZE_UNIT;
long freeMem=Runtime.getRuntime().freeMemory()/SIZE_UNIT;
long maxMem=Runtime.getRuntime().maxMemory()/SIZE_UNIT;
builder.append("总内存:"+totalMem+"Mn");
builder.append("剩余内存:"+freeMem+"Mn");
builder.append("最大内存:"+maxMem+"Mn");
点击进去可查看到getMemoryClass()和getLargeMemoryClass()源码,实际上它们读取的是配置文件中的数值,文件目录位于/system/build.prop。
static public int staticGetMemoryClass() {
// Really brain dead right now -- just take this from the configured
// vm heap size, and assume it is in megabytes and thus ends with "m".
String vmHeapSize = SystemProperties.get("dalvik.vm.heapgrowthlimit", "");
if (vmHeapSize != null && !"".equals(vmHeapSize)) {
return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length()-1));
}
return staticGetLargeMemoryClass();
}
通过adb命令cat /system/build.prop | grep heap,我们可以查看dalvik内存的配置信息,如下是两个手机中的配置信息:
dalvik.vm.heapstartsize=8m
dalvik.vm.heapgrowthlimit=256m
dalvik.vm.heapsize=512m
dalvik.vm.heaptargetutilization=0.75
dalvik.vm.heapminfree=512k
dalvik.vm.heapmaxfree=8m
dalvik.vm.heapstartsize=8m
dalvik.vm.heapgrowthlimit=96m
dalvik.vm.heapsize=256m
dalvik.vm.heaptargetutilization=0.75
dalvik.vm.heapminfree=512k
dalvik.vm.heapmaxfree=8m
getMemoryClass()获取到的是dalvik.vm.heapgrowthlimit的大小,而getLargeMemoryClass()获取到的就是dalvik.vm.heapsize配置项的大小。
dalvik.vm.heapstartsize相当于虚拟机的-Xms配置,该项用来设置堆内存的初始大小。
dalvik.vm.heapgrowthlimit相当于虚拟机的 -XX:HeapGrowthLimit配置,该项用来设置一个标准的应用的最大堆内存大小。一个标准的应用就是没有使用android:largeHeap的应用。
dalvik.vm.heapsize相当于虚拟机的-Xmx配置,该项设置了使用android:largeHeap的应用的最大堆内存大小。
dalvik.vm.heaptargetutilization相当于虚拟机的 -XX:HeapTargetUtilization,该项用来设置当前理想的堆内存利用率。其取值位于0与1之间。当GC进行完垃圾回收之后,Dalvik的堆内存会进行相应的调整,通常结果是当前存活的对象的大小与堆内存大小做除法,得到的值为这个选项的设置,即这里的0.75。注意,这只是一个参考值,Dalvik虚拟机也可以忽略此设置。
dalvik.vm.heapminfree对应的是-XX:HeapMinFree配置,用来设置单次堆内存调整的最小值。
dalvik.vm.heapmaxfree对应的是-XX:HeapMaxFree配置,用来设置单次堆内存调整的最大值。通常情况下,还需要结合上面的 -XX:HeapTargetUtilization的值,才能确定内存调整时,需要调整的大小。
一般情况下Runtime.getRuntime().maxMemory()获取到的最大内存大小跟getMemoryClass()大小相同,但是如果在清单文件AndroidManifest.xml中设置了android:largeHeap="true",这时候获取到的值可能就是getLargeMemoryClass()大小,这里也是一般而言,因为有部分手机中设置了android:largeHeap="true"也无效。还有一点需要注意,使用ActivityManager的getMemoryClass()或者getLargeMemoryClass()获取到的内存值的单位是M,但是Runtime.getRuntime()获取到的是bit。
查看整体内存分配
我们可以使用下面的 adb 命令观察应用内存在不同类型的 RAM 分配之间的划分情况:
adb shell dumpsys meminfo [-d]
-d 标志会打印与 Dalvik 和 ART 内存使用情况相关的更多信息。输出列出了应用的所有当前分配,单位为千字节。
如下是一个简单的demo的dumpsys:
Applications Memory Usage (in Kilobytes):
Uptime: 544929 Realtime: 544929
** MEMINFO in pid 1712 [com.sunny.memory] **
Pss Private Private SwapPss Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 3490 3280 0 0 14336 12849 1486
Dalvik Heap 826 800 0 0 2213 677 1536
Dalvik Other 696 696 0 0
Stack 72 72 0 0
Ashmem 6 0 0 0
Other dev 10 0 8 0
.so mmap 1625 160 20 0
.apk mmap 3032 2316 12 0
.ttf mmap 96 0 0 0
.dex mmap 1794 4 144 0
.oat mmap 457 0 0 0
.art mmap 3876 3680 0 0
Other mmap 2334 4 1736 0
Unknown 458 436 0 0
TOTAL 18772 11448 1920 0 16549 13526 3022
App Summary
Pss(KB)
------
Java Heap: 4480
Native Heap: 3280
Code: 2656
Stack: 72
Graphics: 0
Private Other: 2880
System: 5404
TOTAL: 18772 TOTAL SWAP PSS: 0
Objects
Views: 17 ViewRootImpl: 1
AppContexts: 3 Activities: 1
Assets: 13 AssetManagers: 3
Local Binders: 10 Proxy Binders: 17
Parcel memory: 2 Parcel count: 10
Death Recipients: 0 OpenSSL Sockets: 0
WebViews: 0
SQL
MEMORY_USED: 0
PAGECACHE_OVERFLOW: 0 MALLOC_SIZE: 0
VSS – Virtual Set Size虚拟耗用内存(包含共享库占用的内存)。
RSS – Resident Set Size实际使用物理内存(包含共享库占用的内存)。
PSS – Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)。
USS – Unique Set Size进程独自占用的物理内存(不包含共享库占用的内存)。
USS是针对某个进程开始有可疑内存泄露的情况,是一个程序启动了会产生的虚拟内存,一旦这个程序进程杀掉就会释放!不过USS需要通过root的手机。一般没有root的手机我们可以获取PSS。一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS
有关不同内存的详细信息,这里重点列出ViewRootImpl、AppContexts和Activities。
ViewRootImpl当前活动应用的根视图数量。每个根视图都与一个窗口关联,因此有助于您确定涉及对话框或其他窗口的内存泄漏。
AppContexts 和 Activities当前活动的应用 Context 和 Activity 对象数量。这可以帮助您快速确定由于存在静态引用(比较常见)而无法进行垃圾回收的已泄漏 Activity 对象。这些对象经常拥有很多关联的其他分配,因此成为跟踪大型内存泄漏的一种不错的方式。
有关内存整体分配的更多内容可以参看调查 RAM 使用情况。
小结
本篇主要介绍有关内存溢出的一些概念,以及在Android中如何查看相关内存信息。后续再继续介绍有关内存溢出的几种常见现象,并介绍如何根据不同现象的内存泄漏做出进一步的解决方式。最后会介绍在开发中一旦发生了内存泄漏,该如何使用工具进行探测分析内存溢出。
最后
以上就是爱撒娇羊为你收集整理的android oom 日志分析,Android内存溢出OOM简单介绍的全部内容,希望文章能够帮你解决android oom 日志分析,Android内存溢出OOM简单介绍所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复