这是我参与「第四届青训营 」笔记创作活动的的第8天
打开ART虚拟机的大门
课程回顾
为什么做性能优化、性能优化是什么、如何做性能优化、最佳性能工具选型
课程背景
ART 演进
ART整体架构
01 对象篇
1.1 对象的生命之旅
1.2 类管理
1.2.1 决定一个对象的大小和行为
类主要描述的是一个对象的内存布局和函数信息
内存布局:类成员的大小,类型,和排布
函数信息:主要是虚表的信息:某个函数定义在当前类函数表的第几个位置。因为java是支持继承的,因此类的内存布局和函数虚表需要做继承链全展开以后才能真正确认。(这也是动态性的来源)
1.2.2 基类Object的秘密
java Object基类源码
1.2.3 类加载
一个类分配的对象大小,是由继承链决定的
JAVA的类,是在第一次使用的时候,才会进行加载
1.2.4 内存布局
1.2.5 双亲继承
本质上,双亲委派就是人为划定的一个规矩,目的是为了保证系统内同一个类的一致性
1.2.6 合理的继承抽象的好处
合理的抽象,不是图方便直接塞在顶层基类上,这就是“优雅的架构”带来的现实的好处
1.3 内存分配
1.3.1 分配器
APP的java对象内存分配上是托管到VM来处理的,并不会直接向操作系统去申请,实际上对OS内存的占用和内存布局,是VM控制的(预留-扩展)
1.3.2 不同的分配器的特点
1.3.3 一些典型的场景
1.3.4 内存碎片
ART内存分配的根本原理,还是给使用者在最优的范围内找到一块大小符合的连续内存
1.4 内存回收
1.4.1 两种思路
GC:垃圾回收(Garbage Collection),需要定期查找系统内不用的对象,并且释放占用的内存
RC:引用计数(Reference Counting),指的是对一个对象引用进行计数,多一个引用者,就+1,少一个就-1,为0就释放。典型的如IOS的swift就使用RC进行内存管理
1.4.2 RC的问题和解决
1.4.3 ART的引用
1.4.4 触发GC的条件
1.我们想要不被预期外的GC导致卡顿,可以考虑适当的预留内存
2.大小有上限可预期的情况,new一个大数组,可能比分配一大堆放到容器里面要好。
1.4.5 苹果手机内存少的原因之一
同样是运行三个APP,IOS的每个app内存空间里面都是只有在用内存,不存在浪费
因为GC触发总归是有条件的并且触发会导致卡顿,所以不可能无时无刻都保持清理,必然每个应用都会维持一个垃圾堆,应用多了,积累的垃圾量就很大了
1.4.6 GC的方式
GC roots的概念:怎么判断哪些内存是有用的那些是没用的?
1.4.7 tracing GC
从roots遍历,所有mark的对象是有holder的,释放掉没有holder的object
1.4.8 copying GC
从roots遍历,把有用的的对象拷贝到另一个区域,然后集中释放掉当前区域的内存
1.4.9 ART的做法
1.4.10 回收之后
finalize方法一般用来跟随对象的生命周期,清理掉绑定的native资源
一个对象的finalize方法只会执行一次,再次激活之后的对象是不会触发finalize的。
02 执行篇
2.1 虚拟机的执行方式
2.1.1 虚拟机的执行方式 - JIT
2.1.2 虚拟机的执行方式 - OAT
OAT(或者AOT),和川T不同,是在程序运行之前,对APK中的函数进行编译。
1.和程序是否运行无关
2.编译的范围不是以函数为单位,而是以dex为单位的。
3.结果会持久化。
2.1.3 虚拟机的执行方式 - 延迟绑定
绑定的越迟,动态性越好,性能越差
绑定的越早,动态性越差,性能越好
2.2 栈管理
ART对于解释执行和编译后指令采用两种不同的策略:
1.对于解释执行,栈托管到虚拟机完成。
2对于编译后的,压栈处理和native代码是一样的,遵从对应指令集的约定
2.2.1 不同执行方式之间的切换
对于AOTJIT到解释执行,或者反之的调用,ART采用trampoline-bridge机制来进行切换
2.2.2 简单总结
1.压栈-出栈的速度不同,解释执行的速度慢
2.解释执行的栈结构是托管的,编译执行的栈结构是遵从虚拟机规则的
3.解释执行传递参数有额外的空间成本,编译执行没有
4.不同执行方式之间调用切换采用trampoline/bridge进行
2.3 异常处理
当拿到一个异常的时候,会逐级的回栈,做两个事情:
1.回一级栈看看要不要 2.如果不要就跳出
2.4多线程
多线程必然带来原子性问题,那ART怎么解决的呢?
2.4.1 多线程 —— synchronize
JAVA引入了sync机制,让我们加锁解锁很方便
2.5 同步机制 —— monitor
回到Oject基类的结构中,此时我们就知道,shadow monitor是干嘛用的了,如果我们对一个 对象使用了sync,这个对象就会生成一个lock,保留在这个shadow monitor指向的内存地址中
2.5.1 胖锁和瘦锁
胖锁:费劲,但是响应快,实现上采用spinlock
瘦锁:省力,但是反应慢,实现上采用mutexlock