美文网首页Android开发Android技术知识Android开发经验谈
Android 字节跳动技术二面:程序运行时,内存到底是如何进行

Android 字节跳动技术二面:程序运行时,内存到底是如何进行

作者: Android_until | 来源:发表于2020-11-09 17:00 被阅读0次

    程序运行时,内存到底是如何进行分配的?

    很多人将 Java 的内存分为堆内存(heap)和栈内存(Stack),这种划分方式在一定程度上体现了这两块区域是 Java 工程师最关注的内存区域,但是其实这种划分方式并不完全准确。

    一个 HelloWorld.java 文件被 JVM 加载到内存中的过程

    一、程序计数器(Program Counter Register)

    当某一个线程被 CPU 挂起时,需要记录代码已经执行到的位置,方便 CPU 重新执行此线程时,知道从哪行指令开始执行,这就是程序计数器的作用。

    实际上除了恢复线程操作之外,其它一些我们熟悉的分支操作、循环操作、跳转、异常处理等也都需要依赖这个计数器来完成。

    关于程序计数器还有几点需要格外注意:

    • 在 Java 虚拟机规范中,对程序计数器这一区域没有规定任何 OutOfMemoryError 情况(或许是感觉没有必要吧)。
    • 线程私有的,每条线程内部都有一个私有程序计数器。它的生命周期随着线程的创建而创建,随着线程的结束而死亡。
    • 当一个线程正在执行一个 Java 方法的时候,这个计数器记录的是正在执行的虚拟机字节码指令的地址。如果正在执行的是 Native 方法,这个计数器值则为空(Undefined)。

    二、虚拟机栈

    虚拟机栈也是线程私有的,与线程的生命周期同步。

    在 Java 虚拟机规范中,对这个区域规定了两种异常状况:

    1. StackOverflowError:当线程请求栈深度超出虚拟机栈所允许的深度时抛出。
    2. OutOfMemoryError:当 Java 虚拟机动态扩展到无法申请足够内存时抛出。

    JVM 是基于栈(就是指虚拟机栈)的解释器执行的,DVM 是基于寄存器解释器执行的。

    虚拟机栈的初衷是用来描述 Java 方法执行的内存模型,每个方法被执行的时候,JVM 都会在虚拟机栈中创建一个栈帧。

    1. 栈帧

    栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,每一个线程在执行某个方法时,都会为这个方法创建一个栈帧。

    我们可以这样理解:一个线程包含多个栈帧,而每个栈帧内部包含局部变量表、操作数栈、动态连接、返回地址等。如下图所示:

    • 局部变量表

    局部变量表是变量值的存储空间,我们调用方法时传递的参数,以及在方法内部创建的局部变量都保存在局部变量表中。在 Java 编译成 class 文件的时候,就会在方法的 Code 属性表中的 max_locals 数据项中,确定该方法需要分配的最大局部变量表的容量。

    使用 javap -v 反编译

    注意:系统不会为局部变量赋予初始值(实例变量和类变量都会被赋予初始值),也就是说不存在类变量那样的准备阶段。

    • 操作数栈

    操作数栈(Operand Stack)也常称为操作栈,它是一个后入先出栈(LIFO)。

    同局部变量表一样,操作数栈的最大深度也在编译的时候写入方法的 Code 属性表中的 max_stacks 数据项中。栈中的元素可以是任意Java数据类型,包括 long 和 double。

    • 动态链接

    动态链接的主要目的是为了支持方法调用过程中的动态连接(Dynamic Linking)。

    在一个 class 文件中,一个方法要调用其他方法,需要将这些方法的符号引用转化为其所在内存地址中的直接引用,而符号引用存在于方法区中。

    • 返回地址

    无论当前方法采用正常退出或异常退出,在方法退出后都需要返回到方法被调用的位置,程序才能继续执行。而虚拟机栈中的“返回地址”就是用来帮助当前方法恢复它的上层方法执行状态。

    一般来说,方法正常退出时,调用者的 PC 计数值可以作为返回地址,栈帧中可能保存此计数值。而方法异常退出时,返回地址是通过异常处理器表确定的,栈帧中一般不会保存此部分信息。

    三、本地方法栈

    本地方法栈和上面介绍的虚拟栈基本相同,只不过是针对本地(native)方法。在开发中如果涉及 JNI 可能接触本地方法栈多一些,在有些虚拟机的实现中已经将两个合二为一了(比如HotSpot)。

    四、堆

    Java 堆(Heap)是 JVM 所管理的内存中最大的一块,该区域唯一目的就是存放对象实例,几乎所有对象的实例都在堆里面分配,因此它也是 Java 垃圾收集器(GC)管理的主要区域,有时候也叫作“GC 堆”。同时它也是所有线程共享的内存区域,因此被分配在此区域的对象如果被多个线程访问的话,需要考虑线程安全问题。

    按照对象存储时间的不同,堆中的内存可以划分为新生代(Young)老年代(Old),其中新生代又被划分为 EdenSurvivor 区。具体如下图所示:

    五、方法区

    方法区(Method Area)主要是存储已经被 JVM 加载的类信息(版本、字段、方法、接口)、常量、静态变量、即时编译器编译后的代码和数据。该区域同堆一样,也是被各个线程共享的内存区域。

    注意:关于方法区,很多开发者会将其跟“永久区”混淆。

    • 方法区是规范层面的东西,规定了这一个区域要存放哪些数据。
    • 永久区或者是 metaspace 是对方法区的不同实现,是实现层面的东西。

    六、异常再现

    • StackOverflowError 栈溢出异常

    递归调用是造成 StackOverflowError 的一个常见场景,原因就是每调用一次method方法时,都会在虚拟机栈中创建出一个栈帧。因为是递归调用,method方法并不会退出,也不会将栈帧销毁,所以必然会导致StackOverflowError

    • OutOfMemoryError 内存溢出异常

    理论上,虚拟机栈、堆、方法区都有发生 OutOfMemoryError 的可能。但是实际项目中,大多发生于堆当中。

    在一个无限循环中,动态的向 ArrayList 中添加新的对象。这会不断的占用堆中的内存,当堆内存不够时,必然会产生 OutOfMemoryError,也就是内存溢出异常。

    总结

    对于 JVM 运行时内存布局,我们需要始终记住一点:上面介绍的这 5 块内容都是在 Java 虚拟机规范中定义的规则,这些规则只是描述了各个区域是负责做什么事情、存储什么样的数据、如何处理异常、是否允许线程间共享等。千万不要将它们理解为虚拟机的“具体实现”,虚拟机的具体实现有很多,比如 Sun 公司的 HotSpot、JRocket、IBM J9、以及我们非常熟悉的 Android Dalvik 和 ART 等。这些具体实现在符合上面 5 种运行时数据区的前提下,又各自有不同的实现方式。

    如何进阶Android?

    有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

    对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

    最后我在这里分享一下这段时间从朋友,大佬那里收集到的一些2019-2020BAT 面试真题解析,里面内容很多也很系统,包含了很多内容:Android 基础、Java 基础、Android 源码相关分析、常见的一些原理性问题等等,可以很好地帮助我们深刻理解Android相关知识点的原理以及面试相关知识。

    1、确定好方向,梳理成长路线图

    不用多说,相信大家都有一个共识:无论什么行业,最牛逼的人肯定是站在金字塔端的人。所以,想做一个牛逼的程序员,那么就要让自己站的更高,成为技术大牛并不是一朝一夕的事情,需要时间的沉淀和技术的积累。

    关于这一点,在我当时确立好Android方向时,就已经开始梳理自己的成长路线了,包括技术要怎么系统地去学习,都列得非常详细。

    知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结。

    2、通过源码来系统性地学习

    只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

    真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

    《486页超全面Android开发相关源码精编解析》

    3、阅读前辈的一些技术笔记

    《960全网最全Android开发笔记》

    4、刷题备战,直通大厂

    历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
    包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

    如何使用它?

    1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
    2.五角星数表示面试问到的频率,代表重要推荐指数

    《379页Android开发面试宝典》

    以上内容均放在了开源项目:github 中已收录,大家可以自行获取(或者关注主页扫描加微信获取)。

    相关文章

      网友评论

        本文标题:Android 字节跳动技术二面:程序运行时,内存到底是如何进行

        本文链接:https://www.haomeiwen.com/subject/sftrbktx.html