主要是与某个版本作基准进行对比(一般是最新版本的前一个版本作原数据),优化后,PSS有所下降,线上OOM率减少(Bugly版本对比),泄漏点减少(从捉取一些线上上传回来的内存堆栈信息分析,或本地测试后dump下hprof文件分析)。
App程序中己动态分配的堆内存
,由于某种原因,App程序未释放或无法释放,会造成系统(手机)内存的浪费。长生命周期对象持有短生命周期对象强引用
,从而导致短生命周期对象无法被回收。 我们注意这两个关键词堆内存、强引用
。
什么都不用说,先上张自画图。为大家推荐一本书《深入理解JVM》
第一步,当虚拟机遇到一条new指令时,首先检查这条指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
第二步,如果检查通过后,虚拟机将为这个new出的对象进行分配内存。划分内存是通过指针碰撞、空间列表的组合,同时也考虑并发安全问题(CAS(Compare And Swap的缩写–乐观锁)失败重试、本地线程内存缓冲)。这中是进行内存分配哦,这时候还不能确定对象所需要的内存大小。在类加载完成后才确定内存的大小。
第三步,内存分配完成后,虚拟机将分配到的内存空间进行初始化为零值(默认的初始值),但不包括对象头信息,如果使用TLAB(Thread Local Allocation Buffer ,即线程本地分配缓冲区),这过程可以提前至TLAB分配时进行(Eden区划分出一小块区域作为TLAB)。这一步操作是保证了对象的实例成员(字段)在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
第四步,对象进行必要的设置(主要是一些对象头信息的设置),例如这个对象的运行状态、GC分代年龄、锁状态、属于哪个类的实例、哈希码等等信息。
第五步,从前面几个步骤知道,只是设置了对象头信息和所有类成员(字段)赋为默认的初始值,对象并没有执行方法,所以最后是会接着执行方法,这样才算创建出一个真正可用的对象。几乎所有对象是存放在堆区。
使用一张图,快速了解一下内存分配模型。
编译时就分配好,在程序整个运行期间都存在。它用于存储已经被虚拟机加载的类信息、静态变量、常量等数据。
几乎所有 new 出来的对象是存放在堆区,由 Java 垃圾回收器回收。堆中的对象是垃圾回收的重点。 堆区的划分新生代、老年代:
新生代是用来存放新new出来的对象,划分为 Eden区、From Survivor区 、To Survivor区 。几乎所以的new出来的对象都会存放在Eden区 (如果new出来的对象占用的内存非常大,在新生代中存放不下,直接进入老年代存放)。
当Eden区的内存空间不足时,系统会触发Minor GC /Young GC进行回收Eden区的对象(From Survivor区 、To Survivor区不会触发GC),经过GC后,一些对象仍然存活(对象被引用着–通过GC Root可达性来判断),则会被移到To Survivor区存放,当对象在Survivor区熬过一次GC后,此对象的GC年龄就会+1(GC年龄是对象头信息的一个标记参数),会被复制到From Survivor区,当From Survivor区的对象达到一定年龄时(默认年龄是15,但可以通过XX:MaxTenuringThreshold设置),被移到老年代,否则复制到To Survivor区。
老年代是新生代存放不下的大对象,或对象经过多次Minor GC /Young GC后仍然存活的对象(长期存活的对象)。
当随着Eden区的Minor GC /Young GC持续进行,老年代的对象持续增加,会导致老年代可用的内存空间也会持续减少,最终系统会触发Major GC。
永久代(持久代)是存放包含应用的类/方法信息,以及JRE库的类和方法信息。然而在Java8中,元空间取代了永久代,元空间(Metaspace)被称为“元数据区”。
需要注意的是
:元空间并不在虚拟机中哦,而是使用本地内存(以前永久代是在jvm中的)。这样就解决了以前永久代的OOM问题,元数据和class对象存放在永久代中,容易出现性能问题和内存溢出,毕竟是和老年代共享堆空间。
当方法执行时,会在栈区内存中创建方法体内部的局部变量,方法结束后自动释放内存。
我们都知道,在java中不同的对象存在不同的生命周期的,java对象在JVM中也存放在不同的区域,所以对不同生命周期不同的存放区,采取不同的回收策略,以提高效率。
当Eden区的内存空间不足时,系统触发Minor GC/Young GC, 随着GC持续进行,老年代的对象持续增加,导致老年代的内存空间不足,系统触发Major GC。当堆区或方法区内存空间不足时,系统触发Full GC。
Full GC:清理成本高,系统资源消耗高,对系统性能产生影响,很多性能什么都是针对Full GC进行的。 触发Full GC的条件有:
可达性分析 (java) 通过一系列称之为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所有的引用链,当一个对象到GC Roots有引用链,则说明这个对象存活着;当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的(所谓的垃圾)。
引用计数算法(JVM早期使用的—已经不使用) A对象引用B 对象(+1),同时C对象引用B对象(1+1=2),计数法就是引用一次累加1次,如果没有引用就累减1次,如果归到0时,说明没有引用。缺点:就是相互引用。如A对象引用B对象,同时B对象引用A对象,很难去判断对象是否应该回收。
我们定义对象,应该考虑使用那种引用,多考虑使用软引用
(定义一些还有用但并非必须的对象)或弱引用
(定义非必须对象)。
- 我们也可以利用Android Studio的profiler工具,方便快速查找、观察,简单分析一些对象生成情况。
- 也可以dump下hprof文件,结合MAT深入分析与排查,对象发生是否泄漏。
注意
:MAT打开Android Studio的profiler里dump下hprof文件时,利用AS自带的hprof工具转换一下格式(通过命令hprof-conv -z 原hprof文件 输出hprof文件),不然打开是乱码。
当然检测内存泄漏的工具和方法有很多,就不一一列举了,感兴趣的可以网上查阅一下。
为了帮助到大家更好的全面清晰的掌握好性能优化,准备了相关的核心笔记(还该底层逻辑):https://qr18.cn/FVlo89
https://qr18.cn/FVlo89
启动优化
内存优化
UI优化
网络优化
Bitmap优化与图片压缩优化:https://qr18.cn/FVlo89
多线程并发优化与数据传输效率优化
体积包优化
https://qr18.cn/FVlo89
https://qr18.cn/AQpN4J