Android GC造成的电脑卡顿的原因

GC那些事儿--Android内存优化第一弹 - 简书
GC那些事儿--Android内存优化第一弹
内容太晦涩,先乐一个
接, 作为中内存优化的一个小部分.
由于内存相关知识比较生涩, 内存优化中使用到的相关工具, 也有很多专有名词. 对Java内存管理, GC, Android内存管理, Dalvik/ART等知识有一个理论的认识, 可以让我们更好的使用这些工具, 分析内存问题.
据此, 我们就先从理论入手, 聊聊GC那些事儿.
GC 是 garbage collection 的缩写, 垃圾回收的意思. 也可以是 Garbage Collector, 也就是垃圾回收器.
1.1 垃圾回收器
我们先来解释下Garbage Collector(垃圾回收器).
内存管理, 一直是编程中的一个大的问题. 在较老的语言中, 例如C++语言中, 内存管理是显式的, 也就是说使用者自己申请内存使用, 自己释放内存. 这就是为什么C++语言中除了构造函数, 还有析构函数. 我们在创建对象的时候调用构造函数创建, 系统会在对象结束其作用域的时候调用析构函数, 我们需要做的就是在析构函数中释放掉我们申请的相关资源, 以便释放内存地址.
显然, 这种显式的由编程人员自己控制释放内存的方式很容易出问题, 忘了, 漏了, 都可能导致内存问题. 也不符合程序员要懒的特征.
故而, Java语言中引入了自动内存管理的机制, 也就是垃圾回收器. 大部分的现代面向对象语言, 也都是采用自动内存管理机制.
内存自动管理回收机制可以解决大部分, 但不是所有的内存问题, 这也是为什么我们要讨论内存泄露.
垃圾回收器的职责
垃圾回收器有三大职责:
确保任何被引用的对象保留在内存中;
回收不能通过引用关系找到的对象的内存.
垃圾回收的一般流程
gc process
1.2 相关概念
垃圾回收(GC)
垃圾回收器中有一个进程来做上面的这些事情, 这个进程查找我们的对象引用的关系并释放其内存, 这个进程就是garbage collection(垃圾回收), 也就是我们常说的GC.
Heap和Stack
Heap内存是指java运行环境用来分配给对象和JRE类的内存. 是应用的内存空间.
Stack内存是相对于线程Thread而言的, 它保存线程中方法中短期存在的变量值和对Heap中对象的引用等.
Stack内存, 顾名思义, 是类Stack方式, 总是后进先出(LIFO)的.
我们通常说的GC的针对Heap内存的. 因为Stack内存相当于是随用随销的.
heap&stack
直译GC根, 我们姑且不译了吧.所谓GC Root我们可以理解为是一个Heap内存之外的对象, 通常包括但不仅限于如下几种:
System Class 系统Class Loader加载的类. 例如java运行环境中rt.jar中类, 比如java.util.* package中的类.
Thread 运行中的线程
JNI 中的本地/全局变量, 用户自定义的JNI代码或是JVM内部的.
Busy Monitor 任何调用了wait()或notify()方法, 或是同步化的(synchronized)的东西. 可以理解为同步监控器.
Java本地实例, 还在运行的Thread的stack中的方法创建的对象.
活对象/垃圾
如果这个对象是引用可达的, 则称之为活的(live), 反之, 如果这个对象引用不可达, 则称之为死的(dead), 也可以称之为垃圾(garbage).
这个引用可达与不可达就是相对于GC Root来说的:
2, Java的内存管理机制
2.1 关于JVM
我们平常在查看我们的java版本时, 你会发现:
$ java -version
java version "1.8.0_74"
Java(TM) SE Runtime Environment (build 1.8.0_74-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.74-b02, mixed mode)
其中有个HotSpot VM的东西, 那么这个是什么呢? 和JVM有什么关系呢?
在此简单说下, 以便行文:
JVM, Java虚拟机, 可以简单理解为一种技术思想, 虚拟技术理念.
是JVM的一种实现, 包含了服务器版和桌面应用程序版, 现时由Oracle维护并发布.
我们当前使用的sun(oracle)的java版本(应该是1.3以上)都是内置的HotSpot VM实现. 所以接下来的分析也都是基于HotSpot VM的, 但是还是简称JVM.
2.2 JVM内存区域
JVM使用分代式的内存管理方式, 将Heap分成三代 --- 新生代, 老一代, 持久代.
Hotspot heap structure
Young Generation
所有new的对象.
该区域的内存管理使用minor garbage collection(小GC).
更进一步分成Eden space, Survivor 0 和 Survivor 1 三个部分.
Old Generation
新生代中执行小粒度的GC幸存下来的"老"对象.
该区域的内存管理使用major garbage collection(大GC).
Permanent Generation
包含应用的类/方法信息, 以及JRE库的类和方法信息.
小GC执行非常频繁, 而且速度特别快.大GC一般会比小GC慢十倍以上.大小GC都会发出"Stop the World"事件, 也就是说中断程序运行, 直至GC完成. 这也是我们在中为什么说频繁GC会造成用户感知卡顿.
3, GC的流程
了解了内存Heap的几个区域, 我们再来看下垃圾收集器是怎么利用这几个区域来管理内存和回收垃圾的.
1. 创建新的对象
每当我们使用new创建一个对象时, 这个对象会被分配到新生代的Eden区域:
object allocation
2. 当Eden区域满时当Eden区域内存被分配完时, 小GC程序被触发:
Eden filling
引用可达的对象会移到Survivor(幸存者)区域--S0, 然后清空Eden区域, 此时引用不可达的对象会直接删除, 内存回收, 如下:
3. Eden再次满时当Eden区域再次分配完后, 小GC执行, 引用可达的对象会移到Survivor(幸存者)区域, 而引用不可达的对象会跟随Eden的清空而删除回收.
需要注意的是, 这次引用可达的对象移动到的是S1的幸存者区.而且, S0区域也会执行小GC, 将其中还引用可达的对象移动到S1区, 且年龄+1. 然后清空S0, 回收其中引用不可达的对象.
此时, 所有引用可达的对象都在S1区, 且S1区的对象存在不同的年龄. 如下:
next filling
当Eden第三次满时, S0和S1的角色互换了:
4. 当Survivor区的对象年龄达到"老年线"时上面1~3循环, Survivor区的对象年龄也会持续增长, 当其中某些对象年龄达到"老年线", 例如8岁时, 它们会"晋升"到老年区.
如此1~4步重复, 大体流程是这样的
转载请注明出处, 欢迎大家分享到朋友圈, 微博~
看看书, 写写字; 捣鼓捣鼓小技术.
个人博客: http://blog.lmj.wiki主题信息(必填)
主题描述(最多限制在50个字符)
申请人信息(必填)
申请信息已提交审核,请注意查收邮件,我们会尽快给您反馈。
如有疑问,请联系
傻丫头和高科技产物小心翼翼的初恋
如今的编程是一场程序员和上帝的竞赛,程序员要开发出更大更好、傻瓜都会用到软件。而上帝在努力创造出更大更傻的傻瓜。目前为止,上帝是赢的。个人网站:。个人QQ群:、
个人大数据技术博客:
人生得意须尽欢,莫使金樽空对月。
本文为腾讯 Bugly 公众号投稿,作者:作者:,版权归原作者所有,未经作者同意,请勿转载。
原文地址:
【CSDN 有奖征稿啦】技术之路,共同进步,有优质移动开发、VR/AR/MR、物联网原创文章欢迎发送邮件至 。
导语想写一篇关于 android GC 的想法来源于追查一个魅族手机图片滑动卡顿问题,由于不断的 GC 导致的丢帧卡顿的问题让我们想了很多方案去解决,所以就打算详细的看看内存分配和 GC 的原理,为什么会不断的 GC,GC ALLOC 和 GC COCURRENT 有什么区别,能不能想办法扩大堆内存减少 GC 的频次等等。1、JVM 内存回收机制1.1 回收算法标记回收算法(Mark and Sweep GC)从”GC Roots”集合开始,将内存整个遍历一次,保留所有可以被 GC Roots 直接或间接引用到的对象,而剩下的对象都当作垃圾对待并回收,这个算法需要中断进程内其它组件的执行并且可能产生内存碎片复制算法 (Copying)将现有的内存空间分为两快,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。标记-压缩算法 (Mark-Compact)先需要从根节点开始对所有可达对象做一次标记,但之后,它并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比比较高。分代将所有的新建对象都放入称为年轻代的内存区域,年轻代的特点是对象会很快回收,因此,在年轻代就选择效率较高的复制算法。当一个对象经过几次回收后依然存活,对象就会被放入称为老生代的内存空间。对于新生代适用于复制算法,而对于老年代则采取标记-压缩算法。1.2 复制和标记-压缩算法的区别乍一看这两个算法似乎并没有多大的区别,都是标记了然后挪到另外的内存地址进行回收,那为什么不同的分代要使用不同的回收算法呢?其实 2 者最大的区别在于前者是用空间换时间后者则是用时间换空间。前者的在工作的时候是不没有独立的“mark”与“copy”阶段的,而是合在一起做一个动作,就叫 scavenge(或 evacuate,或者就叫 copy)。也就是说,每发现一个这次收集中尚未访问过的活对象就直接 copy 到新地方,同时设置 forwarding pointer。这样的工作方式就需要多一份空间。后者在工作的时候则需要分别的 mark 与 compact 阶段,mark 阶段用来发现并标记所有活的对象,然后 compact 阶段才移动对象来达到 compact 的目的。如果 compact 方式是 sliding compaction,则在 mark 之后就可以按顺序一个个对象“滑动”到空间的某一侧。因为已经先遍历了整个空间里的对象图,知道所有的活对象了,所以移动的时候就可以在同一个空间内而不需要多一份空间。所以新生代的回收会更快一点,老年代的回收则会需要更长时间,同时压缩阶段是会暂停应用的,所以给我们应该尽量避免对象出现在老年代。2、Dalvik 虚拟机2.1 java 堆Java 堆实际上是由一个 Active 堆和一个 Zygote 堆组成的,其中,Zygote 堆用来管理 Zygote 进程在启动过程中预加载和创建的各种对象,而 Active 堆是在 Zygote 进程 fork 第一个子进程之前创建的。以后启动的所有应用程序进程是被 Zygote 进程 fork 出来的,并都持有一个自己的 Dalvik 虚拟机。在创建应用程序的过程中,Dalvik 虚拟机采用 COW 策略复制 Zygote 进程的地址空间。COW 策略:一开始的时候(未复制 Zygote 进程的地址空间的时候),应用程序进程和 Zygote 进程共享了同一个用来分配对象的堆。当 Zygote 进程或者应用程序进程对该堆进行写操作时,内核就会执行真正的拷贝操作,使得 Zygote 进程和应用程序进程分别拥有自己的一份拷贝,这就是所谓的 COW。因为 copy 是十分耗时的,所以必须尽量避免 copy 或者尽量少的 copy。为了实现这个目的,当创建第一个应用程序进程时,会将已经使用了的那部分堆内存划分为一部分,还没有使用的堆内存划分为另外一部分。前者就称为 Zygote 堆,后者就称为 Active 堆。这样只需把 zygote 堆中的内容复制给应用程序进程就可以了。以后无论是 Zygote 进程,还是应用程序进程,当它们需要分配对象的时候,都在 Active 堆上进行。这样就可以使得 Zygote 堆尽可能少地被执行写操作,因而就可以减少执行写时拷贝的操作。在 Zygote 堆里面分配的对象其实主要就是 Zygote 进程在启动过程中预加载的类、资源和对象了。这意味着这些预加载的类、资源和对象可以在 Zygote 进程和应用程序进程中做到长期共享。这样既能减少拷贝操作,还能减少对内存的需求。2.2 和 GC 有关的一些指标记得我们之前在优化魅族某手机的 gc 卡顿问题时,发现他很容易触发 GC_FOR_MALLOC,这个 GC 类别后续会说到,是分配对象内存不足时导致的。可是我们又设置了很大的堆 Size 为什么还会内存不够呢,这里需要了解以下几个概念:分别是 Java 堆的起始大小(Starting Size)、最大值(Maximum Size)和增长上限值(Growth Limit)。在启动 Dalvik 虚拟机的时候,我们可以分别通过-Xms、-Xmx和-XX:HeapGrowthLimit三个选项来指定上述三个值,以上三个值分别表示表示
Starting Size: Dalvik 虚拟机启动的时候,会先分配一块初始的堆内存给虚拟机使用。
Growth Limit:是系统给每一个程序的最大堆上限,超过这个上限,程序就会 OOM
Maximum Size:不受控情况下的最大堆内存大小,起始就是我们在用 largeheap 属性的时候,可以从系统获取的最大堆大小
同时除了上面的这个三个指标外,还有几个指标也是值得我们关注的,那就是堆最小空闲值(Min Free)、堆最大空闲值(Max Free)和堆目标利用率(Target Utilization)。假设在某一次 GC 之后,存活对象占用内存的大小为 LiveSize,那么这时候堆的理想大小应该为(LiveSize / U)。但是(LiveSize / U)必须大于等于(LiveSize + MinFree)并且小于等于(LiveSize + MaxFree),每次 GC 后垃圾回收器都会尽量让堆的利用率往目标利用率靠拢。所以当我们尝试手动去生成一些几百 K 的对象,试图去扩大可用堆大小的时候,反而会导致频繁的 GC,因为这些对象的分配会导致 GC,而 GC 后会让堆内存回到合适的比例,而我们使用的局部变量很快会被回收理论上存活对象还是那么多,我们的堆大小也会缩减回来无法达到扩充的目的。 与此同时这也是产生 CONCURRENT GC 的一个因素,后文我们会详细讲到。2.3 GC 的类型
GC_FOR_MALLOC: 表示是在堆上分配对象时内存不足触发的 GC。
GC_CONCURRENT: 当我们应用程序的堆内存达到一定量,或者可以理解为快要满的时候,系统会自动触发 GC 操作来释放内存。
GC_EXPLICIT: 表示是应用程序调用 System.gc、VMRuntime.gc 接口或者收到 SIGUSR1 信号时触发的 GC。
GC_BEFORE_OOM: 表示是在准备抛 OOM 异常之前进行的最后努力而触发的 GC。
实际上,GC_FOR_MALLOC、GC_CONCURRENT 和 GC_BEFORE_OOM 三种类型的 GC 都是在分配对象的过程触发的。而并发和非并发 GC 的区别主要在于前者在 GC 过程中,有条件地挂起和唤醒非 GC 线程,而后者在执行 GC 的过程中,一直都是挂起非 GC 线程的。并行 GC 通过有条件地挂起和唤醒非 GC 线程,就可以使得应用程序获得更好的响应性。但是同时并行 GC 需要多执行一次标记根集对象以及递归标记那些在 GC 过程被访问了的对象的操作,所以也需要花费更多的 CPU 资源。后文在 Art 的并发和非并发 GC 中我们也会着重说明下这两者的区别。2.4 对象的分配和 GC 触发时机
调用函数 dvmHeapSourceAlloc 在 Java 堆上分配指定大小的内存。如果分配成功,那么就将分配得到的地址直接返回给调用者了。函数 dvmHeapSourceAlloc 在不改变 Java 堆当前大小的前提下进行内存分配,这是属于轻量级的内存分配动作。
如果上一步内存分配失败,这时候就需要执行一次 GC 了。不过如果 GC 线程已经在运行中,即 gDvm.gcHeap-&gcRunning 的值等于 true,那么就直接调用函数 dvmWaitForConcurrentGcToComplete 等到 GC 执行完成就是了。否则的话,就需要调用函数 gcForMalloc 来执行一次 GC 了,参数 false 表示不要回收软引用对象引用的对象。
GC 执行完毕后,再次调用函数 dvmHeapSourceAlloc 尝试轻量级的内存分配操作。如果分配成功,那么就将分配得到的地址直接返回给调用者了。
如果上一步内存分配失败,这时候就得考虑先将 Java 堆的当前大小设置为 Dalvik 虚拟机启动时指定的 Java 堆最大值,再进行内存分配了。这是通过调用函数 dvmHeapSourceAllocAndGrow 来实现的。
如果调用函数 dvmHeapSourceAllocAndGrow 分配内存成功,则直接将分配得到的地址直接返回给调用者了。
如果上一步内存分配还是失败,这时候就得出狠招了。再次调用函数 gcForMalloc 来执行 GC。参数 true 表示要回收软引用对象引用的对象。
GC 执行完毕,再次调用函数 dvmHeapSourceAllocAndGrow 进行内存分配。这是最后一次努力了,成功与事都到此为止。
示例图如下:通过这个流程可以看到,在对象的分配中会导致 GC,第一次分配对象失败我们会触发 GC 但是不回收 Soft 的引用,如果再次分配还是失败我们就会将 Soft 的内存也给回收,前者触发的 GC 是 GC_FOR_MALLOC 类型的 GC,后者是 GC_BEFORE_OOM 类型的 GC。而当内存分配成功后,我们会判断当前的内存占用是否是达到了 GC_CONCURRENT 的阀值,如果达到了那么又会触发 GC_CONCURRENT。那么这个阀值又是如何来的呢,上面我们说到的一个目标利用率,GC 后我们会记录一个目标值,这个值理论上需要再上述的范围之内,如果不在我们会选取边界值做为目标值。虚拟机会记录这个目标值,当做当前允许总的可以分配到的内存。同时根据目标值减去固定值(200~500K),当做触发 GC_CONCURRENT 事件的阈值。2.5 回收算法和内存碎片主流的大部分 Davik 采取的都是标注与清理(Mark and Sweep)回收算法,也有实现了拷贝 GC 的,这一点和 HotSpot 是不一样的,具体使用什么算法是在编译期决定的,无法在运行的时候动态更换。如果在编译 dalvik 虚拟机的命令中指明了”WITH_COPYING_GC”选项,则编译”/dalvik/vm/alloc/Copying.cpp”源码 – 此是 Android 中拷贝 GC 算法的实现,否则编译”/dalvik/vm/alloc/HeapSource.cpp” – 其实现了标注与清理 GC 算法。由于 Mark and Sweep 算法的缺点,容易导致内存碎片,所以在这个算法下,当我们有大量不连续小内存的时候,再分配一个较大对象时,还是会非常容易导致 GC,比如我们在该手机上 decode 图片,具体情况如下:所以对于 Dalvik 虚拟机的手机来说,我们首先要尽量避免掉频繁生成很多临时小变量(比如说:getView,onDraw 等函数),另一个又要尽量去避免产生很多长生命周期的大对象。3、ART 内存回收机制3.1 Java 堆ART 运行时内部使用的 Java 堆的主要组成包括 Image Space、Zygote Space、Allocation Space 和 Large Object Space 四个 Space,Image Space 用来存在一些预加载的类, Zygote Space 和 Allocation Space 与 Dalvik 虚拟机垃圾收集机制中的 Zygote 堆和 Active 堆的作用是一样的,Large Object Space 就是一些离散地址的集合,用来分配一些大对象从而提高了 GC 的管理效率和整体性能,类似如下图:在下文的 GC Log 中,我们也能看到在 art 的 GC Log 中包含了 LOS 的信息,方便我们查看大内存的情况。3.2 GC 的类型
kGcCauseForAlloc ,当要分配内存的时候发现内存不够的情况下引起的 GC,这种情况下的 GC 会 stop world
kGcCauseBackground,当内存达到一定的阀值的时候会去出发 GC,这个时候是一个后台 gc,不会引起 stop world
kGcCauseExplicit,显示调用的时候进行的 gc,如果 art 打开了这个选项的情况下,在 system.gc 的时候会进行 gc
3.3 对象的分配和 GC 触发时机由于 Art 下内存分配和 Dalvik 下基本没有任何区别,我直接贴图带过了。3.4 并发和非并发 GCArt 在 GC 上不像 Dalvik 仅有一种回收算法,Art 在不同的情况下会选择不同的回收算法,比如 Alloc 内存不够的时候会采用非并发 GC,而在 Alloc 后发现内存达到一定阀值的时候又会触发并发 GC。同时在前后台的情况下 GC 策略也不尽相同,后面我们会一一给大家说明。非并发 GC步骤 1. 调用子类实现的成员函数 InitializePhase 执行 GC 初始化阶段。步骤 2. 挂起所有的 ART 运行时线程。步骤 3. 调用子类实现的成员函数 MarkingPhase 执行 GC 标记阶段。步骤 4. 调用子类实现的成员函数 ReclaimPhase 执行 GC 回收阶段。步骤 5. 恢复第 2 步挂起的 ART 运行时线程。步骤 6. 调用子类实现的成员函数 FinishPhase 执行 GC 结束阶段。并发 GC步骤 1. 调用子类实现的成员函数 InitializePhase 执行 GC 初始化阶段。步骤 2. 获取用于访问 Java 堆的锁。步骤 3. 调用子类实现的成员函数 MarkingPhase 执行 GC 并行标记阶段。步骤 4. 释放用于访问 Java 堆的锁。步骤 5. 挂起所有的 ART 运行时线程。步骤 6. 调用子类实现的成员函数 HandleDirtyObjectsPhase 处理在 GC 并行标记阶段被修改的对象。。步骤 7. 恢复第 4 步挂起的 ART 运行时线程。步骤 8. 重复第 5 到第 7 步,直到所有在 GC 并行阶段被修改的对象都处理完成。步骤 9. 获取用于访问 Java 堆的锁。步骤 10. 调用子类实现的成员函数 ReclaimPhase 执行 GC 回收阶段。步骤 11. 释放用于访问 Java 堆的锁。步骤 12. 调用子类实现的成员函数 FinishPhase 执行 GC 结束阶段。所以不论是并发还是非并发,都会引起 stopworld 的情况出现,并发的情况下单次 stopworld 的时间会更短,基本区别和。3.5 Art 并发和 Dalvik 并发 GC 的差异首先可以通过如下 2 张图来对比下Dalvik GC:Art GC:Art 的并发 GC 和 Dalvik 的并发 GC 有什么区别呢,初看好像 2 者差不多,虽然没有一直挂起线程,但是也会有暂停线程去执行标记对象的流程。通过阅读相关文档可以了解到 Art 并发 GC 对于 Dalvik 来说主要有三个优势点:1、标记自身Art 在对象分配时会将新分配的对象压入到 Heap 类的成员变量 allocation_stack_ 描述的 Allocation Stack 中去,从而可以一定程度缩减对象遍历范围。2、预读取对于标记 Allocation Stack 的内存时,会预读取接下来要遍历的对象,同时再取出来该对象后又会将该对象引用的其他对象压入栈中,直至遍历完毕。3、减少 Pause 时间在 Mark 阶段是不会 Block 其他线程的,这个阶段会有脏数据,比如 Mark 发现不会使用的但是这个时候又被其他线程使用的数据,在 Mark 阶段也会处理一些脏数据而不是留在最后 Block 的时候再去处理,这样也会减少后面 Block 阶段对于脏数据的处理的时间。3.6 前后台 GC前台 Foreground 指的就是应用程序在前台运行时,而后台 Background 就是应用程序在后台运行时。因此,Foreground GC 就是应用程序在前台运行时执行的 GC,而 Background 就是应用程序在后台运行时执行的 GC。应用程序在前台运行时,响应性是最重要的,因此也要求执行的 GC 是高效的。相反,应用程序在后台运行时,响应性不是最重要的,这时候就适合用来解决堆的内存碎片问题。因此,Mark-Sweep GC 适合作为 Foreground GC,而 Mark-Compact GC 适合作为 Background GC。由于有 Compact 的能力存在,碎片化在 Art 上可以很好的被避免,这个也是 Art 一个很好的能力。3.7 Art 大法好总的来看,art 在 gc 上做的比 dalvik 好太多了,不光是 gc 的效率,减少 pause 时间,而且还在内存分配上对大内存的有单独的分配区域,同时还能有算法在后台做内存整理,减少内存碎片。对于开发者来说 art 下我们基本可以避免很多类似 gc 导致的卡顿问题了。另外根据谷歌自己的数据来看,Art 相对 Dalvik 内存分配的效率提高了 10 倍,GC 的效率提高了 2-3 倍。4、GC Log当我们想要根据 GC 日志来追查一些 GC 可能造成的卡顿时,我们需要了解 GC 日志的组成,不同信息代表了什么含义。4.1 Dalvik GC 日志dalvik 的日志格式基本如下:D/dalvikvm: & &, &, &, &
gc_reason:就是我们上文提到的,是 gc_alloc 还是 gc_concurrent,了解到不同的原因方便我们做不同的处理。
amount_freed:表示系统通过这次 GC 操作释放了多少内存
Heap_stats:中会显示当前内存的空闲比例以及使用情况(活动对象所占内存 / 当前程序总内存)
Pause_time:表示这次 GC 操作导致应用程序暂停的时间。关于这个暂停的时间,在 2.3 之前 GC 操作是不能并发进行的,也就是系统正在进行 GC,那么应用程序就只能阻塞住等待 GC 结束。而自 2.3 之后,GC 操作改成了并发的方式进行,就是说 GC 的过程中不会影响到应用程序的正常运行,但是在 GC 操作的开始和结束的时候会短暂阻塞一段时间,所以还有后续的一个 total_time。
Total_time:表示本次 GC 所花费的总时间和上面的 Pause_time,也就是 stop all 是不一样的,卡顿时间主要看上面的 pause_time。
4.2 Art GC 日志I/art: & &, &, &, &, &基本情况和 Dalvik 没有什么差别,GC 的 Reason 更多了,还多了一个 OS_Space_Status
LOS_Space_Status:Large Object Space,大对象占用的空间,这部分内存并不是分配在堆上的,但仍属于应用程序内存空间,主要用来管理 bitmap 等占内存大的对象,避免因分配大内存导致堆频繁 GC。
写在最后:图片来源自网络,特别鸣谢老罗。
是一款专为移动开发者打造的质量监控工具,帮助开发者快速,便捷的定位线上应用崩溃的情况以及解决方案。智能合并功能帮助开发同学把每天上报的数千条
根据根因合并分类,每日日报会列出影响用户数最多的崩溃,精准定位功能帮助开发同学定位到出问题的代码行,实时上报可以在发布后快速的了解应用的质量情况,适配最新的 iOS, Android 官方操作系统,鹅厂的工程师都在使用,快来加入我们吧!问题对人有帮助,内容完整,我也想知道答案
问题没有实际价值,缺少关键内容,没有改进余地
我在一个Activity中有一个大的ListView(ListView中的每个Item填充的东西比较多,还有大量图片),出现的现象就是滑动不流畅,操作出现反应迟延,在LogCat中频繁的打印GC_EXPLICIT,通过网上了解,应该是内错的问题,之所以反应迟钝,滑动不流畅是因为要等待内存释放,重新申请新的内存,但是不知道怎么解决。请有经验的高手指点。
完整的几条logcat:11-17 10:19:45.257: D/dalvikvm(22511): GC_EXPLICIT freed 14K, 52% free K, external K, paused 25ms
11-17 10:19:45.414: D/dalvikvm(22511): GC_EXPLICIT freed 14K, 52% free K, external K, paused 26ms
11-17 10:19:45.722: D/dalvikvm(22511): GC_EXPLICIT freed 39K, 52% free K, external K, paused 25ms
11-17 10:19:45.875: D/dalvikvm(22511): GC_EXPLICIT freed 13K, 52% free K, external K, paused 25ms
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
已经大半年不碰Android了,以下是一些记忆中的经验,供参考。首先确实是内存问题,这些都是DalvikVM的GC日志,格式如下:[GC原因] [释放了多少VM内存], [VM堆内存统计], [外部内存统计], [VM暂停时间]其中[原因]的内容可以参考Android源码中/dalvik/vm/alloc/Heap.h中enum GcReason的定义:typedef enum {
/* Not enough space for an &ordinary& Object to be allocated. */
GC_FOR_MALLOC,
/* Automatic GC triggered by exceeding a heap occupancy threshold. */
GC_CONCURRENT,
/* Explicit GC via Runtime.gc(), VMRuntime.gc(), or SIGUSR1. */
GC_EXPLICIT,
/* GC to try to reduce heap footprint to allow more non-GC'ed memory. */
GC_EXTERNAL_ALLOC,
/* GC to dump heap contents to a file, only used under WITH_HPROF */
GC_HPROF_DUMP_HEAP
} GcR你日志中的GC_EXPLICIT就是说有调用过Runtime.gc()或VMRuntime.gc()(可能是SDK调的)。然后[VM暂停时间]是“hold the whole world”,就是所有东西都停止,会非常影响体验,就是造成你觉得操作卡顿的主要原因。然后是解决方案。在ListView/GridView之类的东西里面展现图片很容易就会遇上内存问题,说白了就是内存里的图片太大内存不足,好点的引发GC卡顿,差点的就直接OOM了。所以有几个解决思路:1. 减少图片文件大小
调整图片大小或是降低图片色彩值或是修改图片格式都可以有效降低图片文件大小。例如色彩较丰富的图片jpg就比png要小一些,能做点九的尽量点九,减少色彩值数量可以显著降低png图片大小,特别是如果可以控制在256种颜色之内就可以启动png的色彩索引方案,加大jpg的压缩比例也可以让图片小很多。此方案的缺点则是图片必须是由我们自己处理的,例如静态资源或是从自己服务器获取的图,可以做预先处理。2. 减少图片在内存中的大小
图片文件有多大并不代表它在内存里就占用多少空间,因为我们可以在图片加载过程中对其数据进行一些修改以达到减少在内存中占用的效果。一般来说有两种办法:
1) 动态修改图片色值,例如从8888改为565这种,以前我们用过对大图片非常有效。
2) 先用仅读取图片的bounds,然后依此缩小图片(代码网上很多),销毁掉大图仅使用小图,再做显示处理,也是一个常用方法。3. 更好的利用内存
首先要理解Android中bitmap用的是native的内存,你可以理解为是C用的那段内存,与java用的不在一起。Android从3.2版本以后加强了这块的管理,使得native内存会随着JVM的GC一起回收,所以会大大减少这块的问题(3.2之前的版本也会回收,但因为与Java GC不同步所以一般回收不及时就会暴内存溢出,因此需要开发者手工调用recycle())然后遵循一条法则:“永远只存放要显示的图片在内存中”,也就是说只有用户看得见的图片才在内存里,看不见的就销毁(recycle()),需要时再重新加载。这点与ListView的滑动窗口是一个道理。要做到这些,recycle()总是需要自己写逻辑(对于3.2之前的版本),但留在JVM内存的那部分可以交给系统来管理,以前较为常用的建议是SoftReference和WeakReference,现在一般建议使用Android自带的缓存类LruCache或LruDiskCache,它会自动按照LRU算法销毁队列尾部的元素(最不常用),而非看GC心情,使用参见 另外楼主也应该尽量减少View的个数,能合并的尽量合并,尽量少使用重量级的View(可以查看View的源码由其成员变量组成来确定是轻是重)。如果以上这些都做了依然有大量的GC,建议在业务可接受的范围内与产品讨论简化界面。
同步到新浪微博
分享到微博?
你好!看起来你挺喜欢这个内容,但是你还没有注册帐号。 当你创建了帐号,我们能准确地追踪你关注的问题,在有新答案或内容的时候收到网页和邮件通知。还能直接向作者咨询更多细节。如果上面的内容有帮助,记得点赞 (????)? 表示感谢。
明天提醒我
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)
我要该,理由是:
扫扫下载 App

我要回帖

更多关于 消逝的光芒卡顿 的文章

 

随机推荐