内存溢出
内存溢出(OutofMemory):系统会给每个APP分配内存也就是HeapSize值。当APP占用的内存加上我们申请的内存资源超过了Dalvik虚拟机的最大内存时就会抛出的OutOfMemory异常。
内存泄漏(MemoryLeak):当一个对象不在使用了,本应该被垃圾回收器(JVM)回收。但是这个对象由于被其他正在使用的对象所持有,造成无法被回收的结果。内存泄漏最终会导致内存溢出。内存泄露的主要原因:长生命周期对象持有短生命周期对象。
内存抖动:内存抖动是指在短时间内有大量的对象被创建或者被回收的现象,主要是循环中大量创建、回收对象。这种情况应当尽量避免。
它们三者的重要等级分别:内存溢出>内存泄露>内存抖动。
内存溢出对我们的App来说,影响是非常大的。有可能导致程序闪退,无响应等现象,因此,我们一定要优先解决OOM的问题。
查询某个应用的内存情况
adb shell dumpsys meminfo pgk
Applications Memory Usage (in Kilobytes):
Uptime: 452183367 Realtime: 1104572907
** MEMINFO in pid 2518 [com.android.systemui] **
Pss Private Private SwapPss Rss Heap Heap Heap
Total Dirty Clean Dirty Total Size Alloc Free
------ ------ ------ ------ ------ ------ ------ ------
Native Heap 52100 51944 132 1144 55188 76948 35459 28523
Dalvik Heap 14195 14108 0 4 22048 50307 12577 37730
Dalvik Other 11627 4660 12 148 21400
Stack 1888 1888 0 40 1904
Ashmem 57 48 0 0 560
Gfx dev 5264 5264 0 0 5264
Other dev 93 0 88 0 456
.so mmap 3243 284 8 48 36456
.jar mmap 1229 0 288 0 29248
.apk mmap 16146 0 12740 0 29624
.ttf mmap 275 0 188 0 588
.dex mmap 312 104 200 0 688
.oat mmap 45 0 0 0 3820
.art mmap 4223 4052 0 68 19480
Other mmap 274 40 92 0 1700
EGL mtrack 36864 36864 0 0 36864
GL mtrack 384 384 0 0 384
Unknown 1106 1104 0 16 1720
TOTAL 150793 120744 13748 1468 267392 127255 48036 66253
App Summary
Pss(KB) Rss(KB)
------ ------
Java Heap: 18160 41528
Native Heap: 51944 55188
Code: 13812 115444
Stack: 1888 1904
Graphics: 42512 42512
Private Other: 6176
System: 16301
Unknown: 10816
TOTAL PSS: 150793 TOTAL RSS: 267392 TOTAL SWAP PSS: 1468
Objects
Views: 1741 ViewRootImpl: 7
AppContexts: 55 Activities: 0
Assets: 57 AssetManagers: 0
Local Binders: 349 Proxy Binders: 118
Parcel memory: 26 Parcel count: 106
Death Recipients: 11 OpenSSL Sockets: 0
WebViews: 0
SQL
MEMORY_USED: 0
PAGECACHE_OVERFLOW: 0 MALLOC_SIZE: 0
其中,需要重点关注的字段有:
NativeHeap:Native代码分配的内存,虚拟机和Android框架分配内存
DalvikHeap:Java对象分配的占据内存。
DalvikOther:类数据结构和索引占据内存。
PSS:Proportional Set Size,记录了实际使用的物理内存(比例分配共享库占用的内存),表示一个进程在 RAM 中实际使用的空间地址大小,它按比例包含了共享库占用的内存。假如有3个进程使用同一个共享库,那么每个进程的 PSS 就包括了 1/3 大小的共享库内存。这种方式表示进程的内存使用情况较准确,但当只有一个进程使用共享库时,其情况和RSS一模一样。当一个进程被销毁时,其占用的那部分PSS又会被按比例地分配给其余使用这些库的进程。
RSS:Resident Set Size 实际使用物理内存(包含共享库占用的内存)。
注:Android系统对DalvikHeap有限制。
应用内存上限
可以通过 adb shell getprop dalvik.vm.heapgrowthlimit 获得应用内存上限
同理,还用相似方法得到下面信息
# 应用内存上限
adb shell getprop dalvik.vm.heapgrowthlimit
# 初始堆大小
adb shell getprop dalvik.vm.heapstartsize
# heapsize 表示不受控情况下的极限堆,表示单个虚拟机或单个进程可用的最大内存,如果存在heapgrowthlimit参数,则以heapgrowthlimit为准.
adb shell getprop dalvik.vm.heapsize
dalvik.vm.heapstartsize:它表示堆分配的初始大小,它会影响到整个系统对 RAM 的使用程度,和第一次使用应用时的流畅程度。它值越小,系统 RAM 消耗越慢,但一些较大应用一开始不够用,需要调用 gc 和堆调整策略,导致应用反应较慢。它值越大,这个值越大系统 RAM 消耗越快,但是应用更流畅。
dalvik.vm.heapgrowthlimit:单个应用可用最大内存,超出就会报 OOM。(仅针对 dalvik 堆,不包括native 堆)
dalvik.vm.heapsize:heapsize 表示不受控情况下的极限堆,表示单个虚拟机或单个进程可用的最大内存在设置了 heapgrowthlimit 的情况下,单个进程可用最大内存为 heapgrowthlimit 值。在 android 开发中,如果要使用大堆,需要在manifest 中指定 android:largeHeap 为 true,这样 dvm heap 最大可达 heapsize。
垃圾回收
垃圾标记算法
- 引用计数法:某个对象被引用时,它的引用计数器就+1,不被引用时,引用计数器就 -1,那些引用计数器为 0 的对象就是被标记为垃圾的对象。目前的主流 JAVA 虚拟机并没有使用该算法。因为它无法解决对象循环引用的问题
- 根搜索算法:选定一些对象作为 CG Roots,向下搜索,如果某个对象是从 CG Roots 向下搜索可达的,则认为不可回收。若某个对象从 CG Roots 向下搜索不可达,则认为可被回收
可作为 CG roots 的对象:
- 虚拟机栈中引用的对象;
- 方法区中类静态属性引用的对象;
- 方法区中常量引用的方法;
- 本地方法区中JNI(Native方法)引用的对象。
四种引用:强软弱虚
- 强引用:重要且必要。Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。示例:Object object=new Object();
- 软引用:重要但不必要。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。对应类:SoftReference
- 弱引用:不重要也不必要。只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。对应类:WeakReference
- 虚引用:形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。对应类:PhantomReference
引用队列 ReferenceQueue
当 SoftReference 、WeakReference 与 PhantomReference 所引用的对象被回收时,该引用对象(注意是 Reference 对象而不是 Reference 对象所引用的对象)就会被添加到 ReferenceQueue 引用队列中,这样我们就可以知道哪些对象被回收了
以 WeakReference 使用为例
ReferenceQueue referenceQueue = new ReferenceQueue<>();
WeakReference<byte[]> weakReference = new WeakReference<byte[]>(bytes, referenceQueue);
弱散列表 WeakHashMap
WeakHashMap,此种 Map 的特点是,当除了自身有对 key 的引用外,此 key 没有其他引用那么此 map 会自动丢弃此值,所以比较适合做缓存。
其实现原理为:使用 WeakReference 当作key来进行数据的存储,当 key 中的引用被回收掉之后,该 WeakReference 会被添加到 ReferenceQueue 中,当使用 WeakHashMap 时,WeakHashMap 会将 检索 ReferenceQueue 中的 key 对象,如果某一个 key 存在于 ReferenceQueue 则将其与对应的 value 一并删除