Android JVM 学习笔记- JVM的结构

0. 结构图

image

1. 运行时数据区总览

  1. 类加载子系统:加载的类信息存放于方法区当中,方法区当中可能还包括运行时常量池信息,包括字符串字面量和数字常量。
  2. Java堆:堆在虚拟机启动的时候建立,它是Java程序最主要的内存工作区域,几乎所有的Java对象实例都存放在Java堆当中,堆空间是所有线程共享的,这是一块与Java应用密切相关的内存空间。
  3. Java栈:是每一个线程所私有的内存空间,Java栈是线程执行密切相关的,线程执行的基本行为是函数间的调用,每次函数调用的数据都是Java栈传递的。不论是JVM还是计算机的操作系统,在函数调用时都需要用到栈,在Intellij idea的Debug界面中,我们就可以看到栈信息:
    image

其中的一项,称为一个栈帧,一个栈帧中,至少包含局部变量表、操作数栈、帧数据区几个部分。当一个函数返回时,栈帧会被从Java栈中弹出。(返回包括return和异常)

  1. 本地方法栈:本地指的是Native,JVM允许Java直接调用本地方法。
  2. PC:Program Counter,即程序计数器,和CPU里面的PC表示的是一个意思,即Java代码当前走到的位置。一个Java线程是在执行一个方法,这个正在执行的方法称为当前方法,如果当前方法不是本地方法,那么就指向正在执行的指令,如果执行的是本地方法,那么PC寄存器的值就是undefined。
  3. 执行引擎:是java虚拟机的最核心组件之一,它负责执行虚拟机的字节码,现代虚拟机为了提高执行效率,会使用即时编译技术将方法编译成机器码后再执行。

其实,我们可以发现,灰色的区域(Java栈、本地方法栈、程序计数器)是线程私有的,而其他的空间则是线程共用的。

2.PC、本地方法栈、 Java栈

这三者都是线程私有的。

PC 程序计数器

PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码,由执行引擎读取下一条的指令。它是一块很小的内存空间,几乎可以忽略不计,也是运行速度最快的存储区域。在JVM规范中每个线程都有自己的程序计数器,是线程私有的,生命周期同线程周期保持一致。

PC寄存器是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等等最基础的工作都要依赖它完成。

它是唯一一个在Java虚拟机规范中,没有规定任何OOM情况的区域。也没有GC的区域

Java 栈

每一次函数的调用,都会在调用栈上维护一个独立的栈帧,栈帧一般包括:

  1. 局部变量表:是一组变量值的存储空间,用来存放方法参数和局部变量,虚拟机通过索引定位的方式使用局部变量表。储存包括八种基本数据类型对象的引用地址,参与方法的调用和返回。

  2. 操作数栈:存放方法调用时的实参,即操作数(在概念模型中各个栈帧都是相互独立的,但是实际实现时,都会使两个独立的栈帧一部分重叠,下面的部分操作数栈与上面的局部变量表重叠在一块区域,这样在方法调用时可以共用一部分的数据。)。

  3. 动态链接:(略,其实就是指向运行时常量池的方法引用)。

  4. 方法返回地址:方法的返回分成两个情况:

    • 首先是支持退出,退出后会根据方法的定义来决定是否要传返回值给上层的调用者;
    • 另一种是异常导致的方法的结束,这种情况不会讲返回值传给上层的调用方法。

不论是异常还是正常,退出方法的时候,都会退出到最开始的调用处。

  1. 如果是正常退出,那么调用者的PC 寄存器即可作为返回地址。
  2. 如果是异常退出,那么就参照异常处理表来确定。

本地方法栈

本地方法本质上时依赖于实现的,虚拟机实现的设计者们可以自由地决定使用怎样的机制来让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 混合编译的策略,特点是:

  1. 应用在安装的时候dex不会再被编译
  2. App运行时,dex文件先通过解析器被直接执行,热点函数会被识别并被JIT编译后存储在 jit code cache 中并生成profile文件以记录热点函数的信息。
  3. 手机进入 IDLE(空闲) 或者 Charging(充电) 状态的时候,系统会扫描 App 目录下的 profile 文件并执行 AOT 过程进行编译。

最后

在这里插入图片描述

全套视频资料:

一、面试合集

在这里插入图片描述

二、源码解析合集

在这里插入图片描述

三、开源框架合集

在这里插入图片描述
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343