Java语言跨平台特性
不同的操作系统有不同的JDK版本,不同的JDK版本都对JVM进行了实现,JVM对JAVA程序进行内存的动态分配。
JVM的内存模型

类装载子系统:JVM中负载加载.class文件的组成部分,由C++编写
字节码执行引擎:不断的读取指令并执行
运行时数据区
栈:线程私有,生命周期与线程的生命周期相同。虚拟机栈描述的是Java方法的执行模型,一个方法的调用就对应着一个栈帧出栈入栈的过程。在活动的线程中位于栈顶的栈帧才是有效的,称之为当前栈帧(执行引擎执运行的所有命令都是针对当前栈帧进行)。与之相关联的方法称为当前方法。虚拟机栈规定了两种异常:如果线程申请的栈的深度大于虚拟机所允许的深度将抛出StackOverFlow异常,如果虚拟机栈无法申请到足够的内存空间,将抛出OOM异常。通过-Xss来指定每个线程所对应的栈的大小。栈帧包含了几个部分:局部变量表、操作数栈、动态链接、方法出口。

局部变量表:局部变量表是一组变量值的存储空间,用于存储方法参数和方法内部定义的局部变量。局部变量表是以变量槽为单位,虚拟机使用局部变量表完成参数值到参数变量列表传递过程,如果实例方法是非静态方法,局部变量表第一位存储的是所属对象实例的引用,在方法中通过this来引用。系统不会为局部变量设置初始值。
代码实例:

局部变量表:

字节码指令:

iconst_1: 将int型(1)推送至栈顶 (操作数栈)
istore_1: 将栈顶int型数值存入第二个本地变量(局部变量表,会将操作数栈的数据进行出栈操作)
iload_1:从局部变量1中装载int类型的值(装载到操作数栈)
操作数栈:虚拟机把操作数栈作为它的工作区——大多是指令都要从这里弹出数据,执行运算然后压回操作数栈。
动态链接:虚拟机运行的时候,运行时常量池保存着大量的符号,这些符号引用可以看作是每个方法的的间接引用。如果代表代表栈帧A的方法想要调用代表栈帧B的方法,虚拟机指令将会以B的符号引用作为参数,但是符号引用并不是直接代表B方法的内存位置,所以在调用之前需要将B的符号引用转换成直接引用,然后通过直接引用才可以访问方法B。如果符号引用是在类加载阶段或者是在第一次使用的时候转换成直接引用,称为静态链接(发生在类的解析过程中,会把静态方法直接替换成所存数据的内存的指针或者句柄,称为静态链接)。如果这种转换发生在方法运行期间,那么这种转换就称为动态链接。
方法出口:记录方法被调用的具体位置信息。
程序计数器:程序计数器是内存中较小的一块空间,是线程私有。用来记录当前线程执行的字节码指令的对应行数。分支、循环、异常、异常处理,线程恢复等都需要依赖程序计数器完成。由于Java虚拟机多线程是通过线程轮流切换完成的,在确定的时候只会执行一个线程的一条指令,因此为了线程切换后能够回到正确的执行位置,每个线程都需要有一块独立的区域来记录当前所执行的字节码指令。这个计数器记录的就是当前线程所执行的字节码指令的行号,如果当前线程执行的是native方法,那么程序技术器存储的值为空。虚拟机中唯一一个不会抛出异常的内存区域。
堆:堆是Java虚拟及所管理的最大的一块内存区域,所有的线程共享的一块数据区域,此区域的主要目的就是存放对象实例,也是垃圾收集器进行回收的主要区域。堆有Eden、Survivor(Survivor0、Survivor1)、Olde区构成【默认大小比例为 Eden + Survivor = 2/3 Olde 1/3,Eden(8/10) Survivor0 (1/10) Survivor (1/10)】。通过-Xms:指定堆的最小值,-Xmx:指定堆的最大值。如果堆没有足够的内存分配空间,将抛出OOM异常。
方法区:方法区主要是用来存储常量、静态变量、类信息,方法区各个线程之间共享。类信息包括:类的权限定名、类的超类权限定名、访问修饰符、字段信息、方法信息(动态链接)、其他信息(指向ClassLoader的指针、对应Class实例的引用。类加载器在加载类到方法区以后会在堆上创建一个对应的Class类型的对象实例,通过这个Class实例开发人员可以访问对象在方法区的定义)。--XX:MaxMetaspaceSize:设置方法区的最大值,默认为-1,不限制大小。--XX:MetaspaceSize:指定方法区的初始大小,以字节为单位,默认为21M,达到改值就会进行full gc进行类的卸载,由于MetaspaceSize的大小会引发full gc,一般将MetaspaceSize的值与MaxMetaspaceSize的值设置相同(8g内存设置为256M)。如果没有足够的内存分配空间,将抛出OOM异常。
JVM整体结构
