JVM内存模型
一,JVM是什么?
Java Virtual Machine
JDK Java Development Kit Java 源语言
JRE Java Runtime Environment Java运行时的环境
二,JVM内存模式
1,JVM 运行时数据-我的代码到底在JVM里面怎么放的?
1.1程序计数器(也叫PC寄存器):
程序计数器是一个较小的内存空间,指向当前线程正在执行的字节码指令,也就是程序计数器里面记录的是当前线程正在执行的那一条字节码指令的地址。 每个线程都有自己单独的程序员计数器; 线程安全
1.1.1 程序计数器的作用:
A,字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
B,在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了
1.1.2 程序计数器的特点:
A,是一块较小的存储空间
B,线程私有。每条线程都有一个程序计数器。
C,是唯一一个不会出现OutOfMemoryError的内存区域。
D,生命周期随着线程的创建而创建,随着线程的结束而死亡。
1.2虚拟机栈(JVM Stack):
Java 虚拟机栈描述的是java运行过程中的内存模型;
Java虚拟机栈会为每一个即将运行的Java方法创建一块叫做“栈帧”的区域,这块区域用于存储该方法在运行过程中所需要的一些信息,这些信息包括:
1. 局部变量表
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量,其中存放的数据的类型是编译期可知的各种基本数据类型、对象引用(reference)和returnAddress类型(它指向了一条字节码指令的地址)。局部变量表所需的内存空间在编译期间完成分配,即在Java程序被编译成Class文件时,就确定了所需分配的最大局部变量表的容量。当进入一个方法时,这个方法需要在栈中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。在方法执行时,虚拟机是使用局部变量表来完成参数值到参数变量列表的传递过程的。
2. 操作数栈
操作数栈又常被称为操作栈,操作数栈的最大深度也是在编译的时候就确定了。32位数据类型所占的栈容量为1,64为数据类型所占的栈容量为2。当一个方法开始执行时,它的操作栈是空的,在方法的执行过程中,会有各种字节码指令(比如:加操作、赋值元算等)向操作栈中写入和提取内容,也就是入栈和出栈操作。Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。因此我们也称Java虚拟机是基于栈的,这点不同于Android虚拟机,Android虚拟机是基于寄存器的。
3. 动态链接
每个栈帧都包含一个指向运行时常量池(在方法区中,后面介绍)中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用,一部分会在类加载阶段或第一次使用的时候转化为直接引用(如final、static域等),称为静态解析,另一部分将在每一次的运行期间转化为直接引用,这部分称为动态连接。
4. 方法出口信息
当一个方法被执行后,有两种方式退出该方法:执行引擎遇到了任意一个方法返回的字节码指令或遇到了异常,并且该异常没有在方法体内得到处理。无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的PC计数器的值就可以作为返回地址,栈帧中很可能保存了这个计数器值,而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。
5. 等
当一个方法即将被运行时,Java虚拟机栈首先会在Java虚拟机栈中为该方法创建一块“栈帧”,栈帧中包含局部变量表、操作数栈、动态链接、方法出口信息等。当方法在运行过程中需要创建局部变量时,就将局部变量的值存入栈帧的局部变量表中。
当这个方法执行完毕后,这个方法所对应的栈帧将会出栈,并释放内存空间。
1.2.1 java 虚拟机栈的特点
A,局部变量表的创建是在方法被执行的时候,随着栈帧的创建而创建。而且,局部变量表的大小在编译时期就确定下来了,在创建的时候只需分配事先规定好的大小即可。此外,在方法运行的过程中局部变量表的大小是不会发生改变的。
B,Java虚拟机栈会出现两种异常:StackOverFlowError和OutOfMemoryError。
a) StackOverFlowError:
若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。
b) OutOfMemoryError:
若Java虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。
StackOverFlowError表示当前线程申请的栈超过了事先定好的栈的最大深度,但内存空间可能还有很多。
而OutOfMemoryError是指当线程申请栈时发现栈已经满了,而且内存也全都用光了。
C,Java虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡
1.3本地方法栈(navtive)
本地方法栈调用的是一些非JAVA写的一些代码,如用C++写的底层操作代码
本地方法栈和Java虚拟机栈实现的功能类似,只不过本地方法区是本地方法运行的内存模型。
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
方法执行完毕后相应的栈帧也会出栈并释放内存空间。也会抛出StackOverFlowError和OutOfMemoryError异常。
1.4 堆
堆使用来存放对象的内存空间,几乎是所有的对象都存放在堆中
1.4.1 堆的特点
A,线程共享
整个Java虚拟机只有一个堆,所有的线程都访问同一个堆。而程序计数器、Java虚拟机栈、本地方法栈都是一个线程对应一个的。
B,在虚拟机启动时创建
C,垃圾回收的主要场所。
D,可以进一步细分为:新生代、老年代。
新生代又可被分为:Eden、From Survior、To Survior。
不同的区域存放具有不同生命周期的对象。这样可以根据不同的区域使用不同的垃圾回收算法,从而更具有针对性,从而更高效。
E,堆的大小既可以固定也可以扩展,但主流的虚拟机堆的大小是可扩展的,因此当线程请求分配内存,但堆已满,且内存已满无法再扩展时,就抛出OutOfMemoryError。
1.5 方法区
Java虚拟机中定义方法区是堆的一个逻辑部分
方法区存放已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等
1.5.1方法区的特点
A,线程共享
方法区是堆的一个逻辑部分,因此和堆一样,线程共享的。整个虚拟机只有一个方法区
B,永久代
方法区的信息一般需要长期存在,而且他是堆的逻辑分区,按照堆的划分方法,我们把方法区称为老年代
C,内存回收效率底
方法区的信息一般需要长期存在,回收一遍内存后可能只有少量信息无效
对方法区的内存回收的主要目标是:对常量池的回收和对类型的卸载
D,java虚拟机规范对方法区比较松
和堆一样,允许固定大小,也允许可扩展大小,还允许不实现垃圾回收
1.5.2 什么是运行时常量池?
方法区存放三种数据:类信息,常量,静态变量、即时编译器编译后的代码。其中常量存储在运行时的常量池中。
我们一般使用 public static final 声明一个常量。这个类被编译后生成class文件,这个类的所有信息就存在这个class文件中。
当这个类被Java虚拟机加载后,class文件中的常量就放在方法区的运行时常量池中,而且在运行是可以在常量池中添加新的常量。
当运行时常量池中的某些常量没有被对象引用,也没有被变量引用,就需要垃圾回收器。
1.6 直接内存
直接内存是值Java虚拟机之外的内存,但也可能被Java虚拟机使用。
在NIO中引入了一种基于通道和缓存的IOf方式,他可以通过本地方法调用Java虚拟机之外的内存,然后通过一个存在Java堆中DirectByteBuffer直接操作该内存,而无需先将外面内存中的数据复制到堆中在操作,从而提高数据操作的效率。
直接内存的大小不受Java虚拟机的控制,当内存不足是就会抛出OOM异常
内存模型总结:
1),Java虚拟机中有两个“栈”:Java虚拟机栈和本地方法栈
这两个栈功能类似,都是Java方法运行过程中的内存模型,内部构造相同,线程私有。不同:Java虚拟栈是Java方法运行过程中内存模型,本地方法栈描述的是Java本地方法运行过程中的内存模型。
2),Java虚拟中有两个“堆”,一个是原本的堆,一个是方法区。方法区是堆的一个逻辑部分。堆中存放对象,方法区存放:常量,静态变量,类信息和即时编译器编译后的代码。
3)堆是Java虚拟机中最大的一块内存区域,也是垃圾回收器的主要工作区域。
4)程序计数器,Java虚拟机栈和本地方法栈都是线程私有的,每个线程都有自己的程序计数器,Java虚拟机栈和本地方法区,并且他们的声明周期和所属的线程一样;
而堆和方法区的线程共享的,在Java虚拟机中只有一个堆,一个方法区,并且在JVM启动是创建,JVM停止是销毁。
三,对象的访问
不同的虚拟机实现对象的访问方式会有所不同:使用句柄和直接指针,
1,使用句柄方式
Java堆中会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象的实例数据和类型数据的各自具体地址信息;
2,直接指针访问方式
Java堆对象的布局中就必须考虑如何放置访问类型数据相关信息,reference中直接存储的就是对象的地址;
对比:
使用句柄访问方式的优点:reference中存放的是稳定的句柄地址,在对象移动时,只改变句柄中的实例数据指针,不会改变reference中的地址;
使用直接指针访问方式优点:访问速度快,减少一次指针定位时间开销。Sun hotspot使用的是指针访问的。