一 JAVA平台无关性
任意的java文件在经过JAVA编译机器编译完成之后转变为 .class文件 , 之后这个.class文件再交给JAVA虚拟机去执行, Class文件的文件格式当中, 精确地定义了类与接口的表示形式,包括在平台相关的目标文件格式中一些细节上的惯例,正如概念所说,Java为了能够实现平台无关性,制定了一套自己的二进制格式,并经常以文件的方式存储,称为Class文件。这样在不同平台上,只要都安装了Java虚拟机,具备Java运行环境[JRE],那么都可以运行相同的Class文件。 - > 之后再交给对应的操作系统进行执行, 这样就保证了JAVA的跨平台运行
如上, 为JVM内存模型的基本结构, 之后围绕此图对于JVM内存的几个方面进行分析
二 运行时数据区
1: 堆空间(线程共享)
创建时间: 堆空间在虚拟机进行创建的时候就已创建
作用: 堆空间承担着为实例对象以及数组 等分配对应需要的空间, 在堆空间中, 又可以分为两个部分, 一个是新生代 , 一个是**老年代,**在新生代下, 又可以被细分为三个小的部分, 分别为(Edge, Survivor from, Survivor to), 这三个部分在内存分配上大概是, 8:1:1. 并且在JVM使用的时候, 只会使用Survivor 新生代的两个当中的一个, 以及Edge区域, 也就是说在虚拟机运行的时候, 一定会有一个Survivor下的一个区域没有使用到, 因此实际上JVM对应堆的新生代的空间利用率, 无论任何时间JVM所能够使用的空间大小均是百分之九十
堆大小: 由新生代以及老年代决定, 为: 堆空间 = 新生代 + 老年代, 堆的大小可通过参数--Xms(堆的初始容量)、-Xmx(堆的最大容量) 来指定。如上图的左下角位置
分代收集算法: JVM采用一定的算法对于JAVA瞬时对象以及长久对象进行回收和释放操作. 瞬时对象 (Transient Object)通常是那些创建后很快就不再需要,或者在一段时间内不会被多个其他对象引用的对象。长久对象 (Persistent Object)则是那些在一段时间内都需要保持活跃状态,并且可能被多个其他对象所引用的对象。大部分的JAVA对象都是瞬时对象 ,召生夕灭, 存在时间十分短, 因此采用复制算法对新生代进行垃圾回收。老年代对象的生命周期一般都比较长,极端情况下会和JVM生命周期保持一致;通常采用标记-压缩算法对老年代进行垃圾回收。
异常处理: 如果出现所需要的堆内存空间小于当前的堆空间的大小的情况, 就会出现栈溢出的异常, 此时Java虚拟机将会抛出一个OutOfMemoryError异常。简称(OOM)。
GC垃圾回收处理: java堆是GC垃圾回收的主要区域。 GC分为两种: Minor GC、Full GC(也叫做Major GC)
Minor GC(简称GC)
Minor GC是发生在新生代中的垃圾收集动作, 所采用的是复制算法。
GC一般为堆空间某个区发生了垃圾回收,新生代(Young)几乎是所有java对象出生的地方。即java对象申请的内存以及存放都在这里进行。java中的大部分对象通常不会长久的存活, 具有朝生夕死的特点。
当一个对象被判定为"死亡"的时候, GC就有责任来回收掉这部分对象的内存空间。
新生代是收集垃圾的频繁区域。
2: 方法区 \ (JAVA8之前别名为永久代 JAVA8之后别名为元空间) (线程共享)
创建时间: 在虚拟机启动时创建
作用: 它存储了每一个类的结构信息, 简单说就是对应的类的的相关结构信息,包括其中的字段等, 例如运行时常量池 、字段 和方法数据 、构造函数 和普通方法的字节码内容、还包括在类、实例、接口初始化时用到的特殊方法。方法区可以看作是一个区分于堆空间的额外的内存存储空间
异常处理: 方法区跟堆一样, 可以自定义对应的内存的最大以及最小值, 方法区的空间大小决定了其可以存放多少的类, 一旦超过了方法区内存所能够存放的类的大小, 那么就会出现 java.lang.OutofMemoryError:PermGen space (JAVA8前)或者java.lang.OutofMemoryError:Metaspace(8以及以后) (加载过多第三方jar包;Tomcat部署项目过多;大量动态的生成反射类)的一系列错误
永久代与元空间:
**相同点:**永久代以及元空间都是方法区的实现形式
永久代与元空间的区别 : JAVA8更新后, 将永久代改写为了元空间, 在结构上, 元空间与永久代不同, 元空间不在虚拟机设置的内存当中 , 而是使用的本地内存
3: JVM栈空间(线程私有)
每一个虚拟机线程都有自己的虚拟机栈, 在JAVA虚拟机栈当中能够存放栈帧, 在栈帧当中, 主要包含局部变量表, 操作数栈, 以及动态链接 三个重要的部分, 还有方法返回地址 以及附加信息
局部变量表:
作用: 局部变量表可以保证在进行方法调用的时候, 其方法参数的传递, 局部变量表的长度在编译(javac)的时候就已经确定, 其是一个数字数组, 存储方法的参数 以及**定义在方法体内部的局部变量.**一个方法所需要输入的参数越多, 其中所设置的局部变量越多, 那么局部变量表所占用的内存就会越大, 从而导致对应的栈帧越来越大, 对应的嵌套调用次数就会减少。另一方面, 被局部变量表直接或者间接引用的数据引用的对象都不会被GC回收机制回收
线程: 局部变量表是建立再线程的栈上的, 是线程私有数据 , 不存在线程安全问题
Slot变量槽: 是局部变量表最基本的存储单元, 一个Slot是32位,
long、double(64位)占两个slot,其他(32位)占一个slot(包括returnAddress)。
当一个实例方法被调用的时候,方法参数和局部变量将会按照顺序被复制到局部变量表中的每一个Slot上。
变量槽可以重复利用,节省资源:如果一个局部变量过了其作用域后,新的局部变量可能会复用其槽位。这就是变量槽的复用, 从而大大的节约空间
操作数栈:
作用: 用于保存计算结果的中间变量, 同时作为计算过程当中的临时变量的存储空间, 其最大的存储空间在编译的时候就确定了, 一个栈的深度为32位, 在调用方法, 刚开始执行的时候, 操作数栈是空的; 如果在方法当中调用了一个有返回值的方法, 其返回值的结果会被压在当前操作数栈中,
栈顶缓存技术: 提出这项技术的原因是因为, 操作数数存储在内存当中的, 频繁的读写必定会影响执行的速度, 所以HotSpot JVM设计者想出这项技术, 采用将栈顶的元素全部都缓存在物理CPU当中, 从而提高对应的读写速度
动态链接:
每一个栈帧内部都包含一个指向运行时常量池(提供一些符号和常量,便于指令的识别) 中该栈帧所属方法的引用。目的是为了支持当前方法的代码能够实现动态链接(Dynamic Linking)。如invokedynamic指令。
作用: 在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用(Symbolic Reference)保存在class文件的常量池里。比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
方法返回地址:
指向本地当中调用此方法的地址, 这样能够保证在当前方法结束之后返回对应原来的位置
4:PC计数器(程序计数器 线程私有)
PC程序计数器可以控制程序执行的顺序, 并且能够在多线程下记录下程序的运行位置. 在任何时刻, 单个得CPU只能运行一个线程, 多线程之间是会相互争抢资源 - CPU的. 这时就需要有个标记, 来标明线程执行到哪里,以保证程序继续运行, 程序计数器便拥有这样的功能,所以, **每个线程都已自己的程序计数器.**每个线程都拥有自己的程序计数器, 每个线程的计数器之间的存储相互独立, 互不影响. 另外, 执行java方法的时候, 其内部的值不为空; 但是执行本地的native方法的时候, 其内部的值为空.
因此程序计数器的生命周期跟PC计数器是同时的, 同生同亡. 其还是唯一不会出现 OutOfMemoryError
的内存区域,随着线程创建的创建而创建, 随着线程的结束而消失.
5: 本地方法栈(线程私有)
本地方法栈跟虚拟机栈之间有很大的相似性, 但是本地方法栈执行的是虚拟机上有关虚拟机本地(native)的相关程序, 而虚拟机栈执行的是虚拟机运行时对应的要执行的JAVA程序.
本地方法栈也有栈帧, 保存着本地方法的 局部变量表, 操作数栈, 动态链接, 出口信息; 也会出现 **StackOverFlowError
和 OutOfMemoryError
**两种错误。