面试宝典之Java程序运行原理

Java运行时数据区:
    主要分为线程共享部分和线程独占部分两个部分
 
线程共享部分:
    所有线程都能访问的内存区域,随着虚拟机或者GC而创建和销毁,主要分为方法区堆内存
 
    方法区:
        JVM用来存储加载的类信息,常量,静态变量,编译后的代码等数据
        方法区在java虚拟机规范中是一个逻辑区划,不同虚拟机的实现方式也可能不同。
        如:Oracle的HotSport,在jdk7种方法区放在永久代,而在1.8种使用元数据空间,并且通过GC来对这个区域进行管理
        注意:
            JDK7种的永久代是堆的一部分,和新生代,老年代的地址是连续的。
            JDK8中的元数据空间属于本地内存,还有一个别名叫非堆(Non-Heap),元空间不存在OOM的情况
 
    堆内存:
        JVM管理的最大的一块内存空间,主要存放类的实例对象。
        堆内存可以是物理上不连续的内存空间,只要逻辑上连续即可,就像我们的磁盘空间一样。
        垃圾回收机制主要管理的就是这块区域。
        堆内存如果满了,会抛出OOM异常
        分代概念:
            JVM中堆空间主要分为新生代(Eden,From Survivor和To Survivor ) 和 老年代
            Eden区占新生代的8/10,from = to = 1/10(默认)
            同一时间,只有一个Survivor区会被使用
        常用参数配置:
           
 
线程独占部分:
    每个线程有独立的空间,随着线程的生命周期而创建以及销毁,主要分为:虚拟机栈本地方法栈程序计数器
    
    虚拟机栈:
        线程私有,生命周期与线程相同,随着线程而创建和销毁
        如果线程请求的栈深度大于虚拟机所允许的最大深度,会抛出StackOverflowError异常
        如果虚拟机栈扩展时无法申请到内存,会抛出OOM异常
        出了native方法,几乎所有方法都是通过虚拟机栈来实现方法调用和执行的(需要程序计数器,堆,方法区的配合)
        线程栈由栈帧组成。
        栈帧:
            每个方法执行的同时会创建一个栈帧。
            一个方法从调用到执行完成的过程,对应这一个栈帧在虚拟机栈中入栈到出栈的过程。
            在活动线程中,只有位于栈顶的栈帧才有效,成为当前栈帧,与当前栈帧相关联的方法称为当前方法。执行引擎运行的所有字节码命令都只针对当前栈帧。
            栈帧随着方法调用而创建,随着方法结束而销毁。
            栈帧内容:
                局部变量表,操作数栈,动态链接,方法返回地址和一些附加信息。
                局部变量表:
                    储存方法参数和方法内部定义的局部变量:储存的是编译器已知的基本数据类型,对象引用和returnAddress类型(指向字节码指令的地址)
                    该方法所需要分配局部变量表的最大容量在将Java文件编译未Class文件时确定,在编译时完成内存空间分配,运行期间不会改变大小
                    局部变量表的最小容量是以变量槽为单位(32位长度的内存空间),对于64位长度数据类型(long,double),虚拟机会使用高位对其的方式为其分配
                两个连续的变量槽
                    虚拟机通过索引定位的方式查找相应的局部变量
                操作数栈:
                    用于计数的临时数据存储区
                    随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或者变量写入操作数栈,随着计算的进行,将战中元素出栈到局
                部变量表或返回方法调用者,也就是出栈入栈操作
                动态链接(Dynamic Linking):
                    在一个class文件中,一个方法要调用其他方法,需要将这些方法的符号引用转化为其在内存地址中的直接引用,而符号引用存在于运行时常量池。
                    每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用。
                    这些符号引用一部分会在类加载阶段或者第一次使用时就直接转化为直接引用,这类转化称为静态解析。另一部分将在每次运行期间转化为直接引
                用,这类转化称为动态连接。
                方法返回
                    一个方法开始执行后,只有两种退出方式:正常完成出口和异常完成出口
                    正常完成出口:
                        方法正常完成并退出,根据当前方法返回的字节码指令,这时有可能会有返回值传递给方法调用者(调用它的方法),或者无返回值。
                    异常完成出口:
                        方法执行过程中遇到异常,并且这个异常在方法体内部没有得到处理,导致方法退出。
                        无论采用何种退出方式,在方法退出后,都需要返回到方法被调用的位置,方法返回时可能需要在栈帧中保存一些信息。
                        一般来说,方法正常退出时,调用者的程序计数器的值可以作为返回地址,栈帧中会保存这个计数器值。而方法异常退出时,返回地址要通过异
                    常处理器表来确定,栈帧中一般不保存这部分信息。
                附加信息:
                    虚拟机规范允许具体的虚拟机实现增加一些规范中没有描述的信息到栈帧之中,例如和调试相关的信息,这部分信息完全取决于不同的虚拟机实现。
                    在实际开发中,一般会把动态连接,方法返回地址与其他附加信息一起归为一类,称为栈帧信息。
    
    程序计数器:
        线程私有(方便多线程时线程的切换),不会出现OOM情况
        可以看作是当前线程执行的字节码的行号指示器。
        字节码解释器工作时,会通过改变这个计数器的值来选区下一条要执行的字节码命令。
        分支,循环,跳转,异常处理,线程恢复都需要依赖程序计数器。
        执行native方法时,程序计数器为空(因为本地方法大多为C实现,不使用上述Java字节码的概念)
 
    本地方法栈:
        本地方法栈为虚拟机使用到的 Native 方法服务。
        Java 程序调用本地方法
            不同于虚拟机栈的入/出栈,当线程调用 native 方法时,虚拟机只是简单地动态连接并直接调用指定的 native 方法。 
        本地方法接口回调
            JVM 中的 Java 方法如果某个虚拟机实现的本地方法接口是使用 C 连接模型的话,那么他的本地方法栈就是 C 栈,当一个 C 函数调用另一个 C 函数时,它
        的栈操作是确定的。如果本地方法接口需要回调JVM 中的 Java 方法,该线程会保存本地方法栈的状态并进入到另一个Java栈。 
        不同虚拟机的不同实现
            虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。常用的 HotSpot 虚拟机选择合
        并了虚拟机栈和本地方法栈。
posted @ 2020-07-23 17:42  星辰河流  阅读(191)  评论(0编辑  收藏  举报