0. 结构图
1. 运行时数据区总览
-
类加载子系统
:加载的类信息存放于方法区
当中,方法区
当中可能还包括运行时常量池信息,包括字符串字面量和数字常量。 -
Java堆
:堆在虚拟机启动的时候建立,它是Java程序最主要的内存工作区域,几乎所有的Java对象实例都存放在Java堆当中,堆空间是所有线程共享的,这是一块与Java应用密切相关的内存空间。 -
Java栈
:是每一个线程所私有的内存空间,Java
栈是线程执行密切相关的,线程执行的基本行为是函数间的调用,每次函数调用的数据都是Java栈传递的。不论是JVM还是计算机的操作系统,在函数调用时都需要用到栈,在Intellij idea的Debug界面中,我们就可以看到栈信息:
其中的一项,称为一个栈帧
,一个栈帧中,至少包含局部变量表、操作数栈、帧数据区几个部分。当一个函数返回时,栈帧会被从Java栈中弹出。(返回包括return和异常)
-
本地方法栈
:本地指的是Native
,JVM允许Java直接调用本地方法。 -
PC
:Program Counter,即程序计数器
,和CPU里面的PC表示的是一个意思,即Java代码当前走到的位置。一个Java线程是在执行一个方法,这个正在执行的方法称为当前方法,如果当前方法不是本地方法,那么就指向正在执行的指令
,如果执行的是本地方法,那么PC寄存器的值就是undefined。 -
执行引擎
:是java虚拟机的最核心组件之一,它负责执行虚拟机的字节码,现代虚拟机为了提高执行效率,会使用即时编译技术将方法编译成机器码后再执行。
其实,我们可以发现,灰色的区域(Java栈、本地方法栈、程序计数器)是线程私有的,而其他的空间则是线程共用的。
2.PC、本地方法栈、 Java栈
这三者都是线程私有的。
PC 程序计数器
PC寄存器
用来存储指向下一条指令的地址,也即将要执行的指令代码,由执行引擎读取下一条的指令。它是一块很小的内存空间,几乎可以忽略不计,也是运行速度最快的存储区域。在JVM规范中每个线程都有自己的程序计数器,是线程私有的,生命周期同线程周期保持一致。
PC寄存器是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等等最基础的工作都要依赖它完成。
它是唯一一个在Java虚拟机规范中,没有规定任何OOM情况的区域。也没有GC的区域。
Java 栈
每一次函数的调用,都会在调用栈上维护一个独立的栈帧,栈帧一般包括:
局部变量表:是一组变量值的存储空间,用来存放方法参数和局部变量,虚拟机通过索引定位的方式使用局部变量表。储存包括
八种基本数据类型
、对象的引用地址
,参与方法的调用和返回。操作数栈:存放方法调用时的实参,即操作数(在概念模型中各个栈帧都是相互独立的,但是实际实现时,都会使两个独立的栈帧一部分重叠,下面的部分操作数栈与上面的局部变量表重叠在一块区域,这样在方法调用时可以共用一部分的数据。)。
动态链接:(略,其实就是指向运行时常量池的方法引用)。
-
方法返回地址:方法的返回分成两个情况:
- 首先是支持退出,退出后会根据方法的定义来决定是否要传返回值给上层的调用者;
- 另一种是异常导致的方法的结束,这种情况不会讲返回值传给上层的调用方法。
不论是异常还是正常,退出方法的时候,都会退出到最开始的调用处。
- 如果是正常退出,那么调用者的PC 寄存器即可作为返回地址。
- 如果是异常退出,那么就参照异常处理表来确定。
本地方法栈
本地方法本质上时依赖于实现的,虚拟机实现的设计者们可以自由地决定使用怎样的机制来让Java程序调用本地方法。
3. Java 堆
一个JVM只存在一个堆内存
,堆是Java内存管理的核心区域。它在JVM虚拟机创建时即被创建,大小就已经确定了,是JVM所管理的最大的一块内存空间。堆在物理上不要求其连续,但是在逻辑上要求是连续的。所有的线程共享Java堆,可以在堆中划分线程私有的缓冲区
。
Java 7 以前的堆内存区分
区分为 : 新生代 + 老年代 + 永久代
Java 8 及之后的堆内存区分
区分为:新生代 + 老年代 + 元空间
1.内存划分和JVM的一项重要的功能:GC(垃圾回收)有非常大的关系,不同的对象,不同区的对象GC回收的频率不同,主要参照GC算法。
2. 我们创建的对象(相当多的一部分都是临时对象),存放在新生代,使用相对短的时间即可被回收的,如果没有被回收的就说明,这个对象的使用频率相对来说高,多次GC都不能回收。而永久代则用于存储class、运行时常量池、字段、方法、代码、JIT代码等。
3. 永久代和方法区是什么关系? 永久代是方法区的一种实现,我们规定类加载后生成的Class对象放在方法区当中,这是JVM的规范的一部分,但是并没有说:必须开辟一个内存,名称为方法区。实际上我们选择在永久代中构建方法区,来存储这些对象。也不是所有虚拟机都有永久代的,我们使用的HotSpot的JVM中的虚拟机只存在于JDK7之前,JDK8及之后被MetaSpace(元空间)所替代了。
4. GC算法和堆内存的划分是什么关系? GC算法主要是根据一些列的规则判断该对象是否还有存活的价值,及时释放无关对象可以节省空间。而堆内存的划分则和GC的频率有关系,各自划分内的GC算法也不相同。
- 新生代:GC的频率相对较高。
- 老年代:GC的频率相对较低。
- 永久代:主要是GC一些卸载的类和废弃的常量。
4. 执行引擎
字节码读入JVM后,机器并不能读懂字节码,只有JVM自身能读懂它,所以JVM必须做一件事情,将JVM翻译成相关平台的机器码(01),具体的过程在这章不做展开。这里讲讲在Android中使用的JIT技术和AOT技术,前者是动态编译,而后者是静态编译。
即时编译(Just-in-Time)
早期的Android(Android 2.1及之前)应用程序执行Java代码实际上是解释器将每个Java指令 翻译成 等价的几条微处理器指令,并且,根据转译后的指令一条一条地按照次序进行执行。这样的执行方式需要在执行前进行翻译,运行是可想而知的低效。
Android2.2 之后 引入了JIT技术,该技术简单来说就是:每遇到一个Class文件,JIT就会对这个类进行编译,生成相当精简的二进制码,花费少许的编译时间来换取后续执行的速率。JIT的提出对性能提升是比较大的,但是确实十分有限的。因为某些Java文件是极少执行的,编译他们的时间有可能远远长于翻译器翻译他们的时间。整体下来,花费的时间并没有减少。
后来,基于JIT的经验,又提出了动态编译器,动态地预判那些需要编译,哪些需要翻译,所以动态编译器是既包含了编译器、又包含了解释器的。
Java文件进行编译后,都是字节码文件(ByteCode),不论是传统的JVM,或者是Dalvik、ART虚拟机,只是各种虚拟机下的ByteCode的组成方式有所差异。其中,JVM对应的运行文件是.Class文件,而Dalvik对应的则是.dex文件,DVM专门对移动操作系统的特性进行了优化,并且基于寄存器进行设计。指令集具有很大的不同。(基于寄存器进行设计的内容可参考RISC:即精简指令系统计算机,指令精简且关键,更多地依赖寄存器来获取更快的速度和更低的功耗)
AOT(Ahead-Of-Time)
Ahead-Of-Time技术。
ART模式(Android runtime)作为一个可选功能在Android 4.4的开发者选项中首次出现,作为Davlik的备选项,ART在安装时就会进行编译动作,编译的文件也不再是一个字节码,而是具体的可执行文件,可以运行在更为底层的硬件上,这样在运行时,就省下了预编译和翻译的时间。该可执行文件本质上是一个ELF文件(是一种用于二进制文件、可执行文件、目标代码、共享库、核心转储格式文件),在Android中,我们无法找到显式存在的OAT文件,其实OAT文件仍然是以.odex作为后缀的,通过file命令或者UE打开可以看到ELF头部。
ART设计是考虑兼容性的,即使是早期编译的Android Project中的Dex文件也可以在运行ART模式的Android设备上使用。这是通过dex2oat做到的,dalvik下的dex、odex文件均可以通过这个工具转化为oat文件,并且odex文件将比dex文件编译的更快。
Odex文件即Optimize Dex,对dex文件的优化,最直观的好处:decodex在系统第一次开机时,需要提取所有APK中的Dex文件,而Odex优化是提前提取出来了,这样运行速度和开机速度都有提高。其次Odex优化后,APK中可以没有Dex文件,而为Odex在Apk包中有一份Dex文件,在/data/dalvik-cache下还有提取出来的一份,浪费存储空间。一定程度上保护了硬件厂商自己的APK,因为APK中只有资源文件,反汇编并没有意义。具体的代码都在Odex之中了。
也正是因为AOT技术,在搭载Android 5.0系统的机器上,初始化后或者是初次开机启动非常的慢,因为系统会提取所有App的dex字节码,优化并且拷贝到/data/dalvik-cache缓存目录中,因此,第一次启动耗时会明显更高。
在Android 7.0 中,JIT被重新启用,采用AOT/JIT 混合编译的策略,特点是:
- 应用在安装的时候dex不会再被编译
- App运行时,dex文件先通过解析器被直接执行,热点函数会被识别并被JIT编译后存储在 jit code cache 中并生成profile文件以记录热点函数的信息。
- 手机进入 IDLE(空闲) 或者 Charging(充电) 状态的时候,系统会扫描 App 目录下的 profile 文件并执行 AOT 过程进行编译。
最后
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集