JVM的内存区域
JVM的运行时数据区域
java虚拟机启动的时候会从物理内存中映射一块内存,纳入虚拟机的虚拟内存管理中,这块儿被 虚拟机管理的虚拟内存就是Java虚拟机的运行时数据区域。 而这块区域被 JVM划分为几个不同的区域来管理。这几个不同的区域分别是:
- 方法区
- java堆
- java虚拟机栈
- 本地方法栈
- 程序计数器
同时按照与线程的关系来分的话,又可以分为:线程私有区域 和 线程共享区域。
对于线程私有区域来说,它独立拥有一份内存区域,这块区域其它线程是无法访问的。
而对于共享内存区域:它对于所有的线程来说,每个线程都可以访问。
除上面的几个区域划分外,JVM还可以向内核直接申请内存用于程序运行,而这块内存的可以称为直接内存,或者叫堆外内存。它一般是由java提供的unsafe类来进行管理,申请这样的内存,由于它不属于JVM和内存管理范围 ,需要自己手动去释放。
示意图:
JVM的执行流程
当执行程序时,会先启动一个虚拟机,虚拟机启动的时候会根据用户配置参数或者默认参数向系统申请内存。JVM获得内存空间后,会根据参数配置将这块内存进行初始化,成为虚拟机的各个运行时数据区域:方法区、堆、虚拟机栈,本地方法栈,程序计数器。然后会将class文件中的类(这里的类一般都是批系统类),静态变量,对象引用,常量等加载到方法区备用。然后创建一个main线程,调用线程中的main方法,之后就进入用户程序的执行流程。
总结起来分为以下几步:
JVM执行过程中产生类,变量,静态变量,常量,对象,等的存储位置
首先,看一下方法区,这是在JVM开始执行程序代码之前要初始化好数据的地方,
方法区存放的数据有:
类对象:当我们声明一个类时,经常会用到这样的代码:类名.class,这代表的是一个类对象,这种数据在类加载后会放入JVM的方法区。
常量:类似: final int a = 0; final long b = 3; String str = “i’m a string”;通过final定义的常量,和字符串字面量,都会放入方法区。
静态变量:像static int a = 0; static Person mPerson;这种静态变量和静态对象引用也会放入方法区,而静态对象引用指向的对象一般会放在Java堆中。
字面常量和符号引用:java代码编译产生的 .class文件中有一些 Constant pool,数据这些数据就是一些符号引用,它们都会放入运行时常量池中,而运行时常量池属于方法区的一部分,所以它们也在方法区中。
类加载器:用于加载类的类,也会放入方法区当中。
堆中存入的数据
Java堆是所有线程共享的内存区域,这里存放的数据,所有线程都可以访问,这里一般会类的对象实例,这些对象实例,可能是由new 关键字产生,也可以是通过反射初始化出的实例。总之只要是类的实例它都会存入这块区域。
JVM的垃圾回收也主要是针对Java堆进行的。
程序计数器数据:
它存储的数据比较简单,它存储的是线程方法中代码指令的执行序号,当一条指令准备执行时,会将它的指令序号存入程序计数器,用以记录程序的执行位置。当一条指令执行完成后,会将下一条指令存入,直到方法中的指令全部执行完毕。当执行下一个方法时,程序计数器会重新记录下一个方法的指令序号。
如下图(通过javap反编译的字节码指令),程序计数器记录是框中的序号数据:
虚拟机栈:
在线程的方法调用过程中会产生一个个方法栈帧,这些栈帧就会存储在虚拟机栈中,栈帧于虚拟机栈采用先进后出(FILO)方式。每一个栈帧中会存储该方法的,本地变量表,操作数栈,地口地址(返回调用它的方法),动态链接信息,
局部变量表和操作栈:虚拟机在执行字节码指令时,会操作方法中声明的变量和常量值。操变量,会存入局部变量表,而操作数会存入操作数栈,这个操作数栈也是采用后进先出的结构的。
方法出口:当一个方法中的指令全部执行完时,它需要返回调用它的地方,方法出口就是记录调用方法的入口地址的。
上面的操作数栈和局部变量表是相互依赖作用的,执行引擎在执行指令时会同时用到它们,比如在java代码中写: int i = 0;这行代码经过java编译器编译成字节码后,会形成两条指令
- 将0入操作数栈
- 将0从操作数栈中取出,保存到局部变量表中的i变量中。
本地方法栈
本地方法栈与java虚拟机栈的作用类似,在hotspot虚拟机当中干脆将虚拟机栈和本地方法栈合并实现了。
量表中的i变量中。
本地方法栈
本地方法栈与java虚拟机栈的作用类似,在hotspot虚拟机当中干脆将虚拟机栈和本地方法栈合并实现了。
|