Java虚拟机

转载:Java虚拟机

Java虚拟机执行流程

Java虚拟机

Java虚拟机结构

Java虚拟机

类加载器子系统

1)Bootstrap ClassLoader(引导类加载器)

加载一下目录的类库:

  • %JAVA_HOME%/jre/lib目录
  • Xbootclasspath参数指定的目录

    Java虚拟机的启动时通过引导类加载器创建一个初始类来完成的。

2)Extensions ClassLoader(拓展类加载器)

用于加载Java的拓展类,用来提供除了系统类之外的额外功能,加载以下目录的类库:
- 加载%JAVA_HOME%jre/lib/ext目录
- 系统属性java.ext.dir所指定的目录

3)Application ClassLoader(应用程序类加载器)

又称SystemClassLoader(系统类加载器),因为这个类加载器可以通过ClassLoader.getSystemClassLoader()方法获取到。加载以下目录的类库:

  1. 当前应用程序Classpath目录
  2. 系统属性java.class.path指定的目录

    用户自定义加载器,则是通过继承java.lang.ClassLoader类的方式来实现自己的类加载器。
    类加载器子系统 除了要加载Class文件到Java虚拟机中,还必须负责验证被导入的Class类的正确性,为类变量分配并初始化,以及帮助解析符号应用,这些动作必须严格按照以下顺序进行:

    加载:查找并加载Class文件
    . 链接:验证、准备、解析

验证:确保被导入类型的正确性
准备:为类的静态字段分配字段,并用默认值初始化这些字段
解析:根据运行时常量池的符号应用来动态决定具体值的过程

3.初始化:将类变量初始化为正确初始值

数据类型

基本数据类型 和 引用类型。
Java虚拟机希望编译器在编译期间尽可能完成类型检查,使得虚拟机在运行期间无需进行类型检查操作。

运行时数据区域

1、程序计数器

为了保证程序能够连续执行下去,处理器必须具有某些手段来确定下一条指令的地址,二而程序计数器正是起到这种作用

程序计数器(Program Conunter Register)也叫PC寄存器,是一块较小的内存空间。在虚拟机概念模型中,字节码解释器工作时就是通过改变程序计数器来选取下一条需要执行的字节码指令,Java虚拟机的多线程是通过轮流切换并分配处理器执行时间的方式来实现的,在一个确定的时刻只有一个处理器执行一条线程中的命令,为了在线程切换厚能恢复到正确的执行位置,每一个线程都会有一个独立的程序计数器,因此,程序计数器是私有的,如果线程执行的方法不是Native方法,则程序计数器保存正在执行的字节码指令地址,如果是Native方法则程序计数器的值则为空()Undefined)。程序计数器是Java虚拟机规范中唯一没有规定任何内存溢出情况的数据区域。

2、Java虚拟机栈

每一条Java虚拟机线程都有一个线程私有的Java虚拟机栈,他的生命周期与线程相同,与线程是同时创建的。java虚拟机栈存储线程中Java方法调用的状态,包括局部变量、参数、返回值以及运算的中间结果等。一个Java虚拟机包含了多个栈帧,一个栈帧用来存储局部变量表、操作数栈、动态链接、方法出口等信息。当线程调用一个Java方法时,虚拟机压入一个新的栈帧到该线程的Java栈中,当该方法执行完成,这个栈帧就从Java栈中弹出。我们平常说的栈内存就是Java虚拟机栈。

  • 如果线程请求分配的栈容量超过Java虚拟机所允许的最大容量,Java虚拟机会抛出*Error.
  • 如果Java虚拟机栈可以动态扩展(大部分Java虚拟机都可以动态扩展),但是扩展时无法申请到足够的内存,或者在创建新线程时没有足够的内存去创建对应的Java虚拟机栈,则会抛出OutOfMemoryError异常。

3、本地方法栈
Java虚拟机实现可能用到的 C 栈(Stacks)来支持Native语言,这个C Stacks就是本地方法栈(Native Method Stack)。它与Java虚拟机类似,只不过本地方法栈是用来支持Native方法服务。如果Java虚拟机不支持Native方法,并且也不依赖C stacks,可以无需支持本地方法栈。在Java虚拟机规范中对本地方法栈的语言和数据结构等没有强制规定,因此具体的Java虚拟机可以*实现它。

4、Java堆
Java堆(Java Heap)是被所有线程共享的运行时内存区域,Java堆用来存放对象实例,几乎所有对象实例都是在这里分配内存。Java堆存储的对象被垃圾收集器管理,这些受管理的对象无需也无法显示的销毁。从内存回收的角度,Java堆可以粗略的分为新生代和老生代。从内存分配的角度,Java堆中可能划分出多个线程私有的分配缓冲区。不管如何划分,Java堆内存的内容是不变的,进行划分是为了更快的回收或分配内存。

Java堆的容量可以是固定的,也可以动态扩展,Java堆所使用的内存在物理上不需要连续,逻辑上连续即可。

Java虚拟机规范中定义了一种异常情况:
如果堆中没有足够的内存来完成实例分配,并且堆无法进行扩展时,则会跑出OutOfMemoryError异常。

5、方法区

方法区(Method Area)是被所有线程共享的运行时内存区域。用来存储已经被Java虚拟机加载的类的结构信息。包括:
运行时常量池、字段和方法信息、静态变量等数据。方法区是Java堆得逻辑组成部分,它一样在物理上不需要连续,并且可以选择在方法区中不实现垃圾收集器。方法区并不等同于永久代,只是因为HotSpot VM使用永久代来实现方法区,对于其他的Java虚拟机,并不存在永久代概念。

Java虚拟机规范中定义了一种异常情况:

如果方法区的内存空间不满足内存分配需求时,Java虚拟机会抛出OutOfMemoryError异常。

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分,Class文件不仅包含类的版本、接口、字段和方法等信息,还包含常量池用来存放编译时期生成的字面量和符号引用,这些内容会在类加载后放在方法区的运行时常量池中、运行时常量池可以理解为类或接口的常量池的运行时表现形式。

Java虚拟机规范中规定了一种异常情况:
当创建类或接口时,如果构造运行时常量池所需的内存超过了方法去所提供的最大值,Java虚拟机会抛出OutOfMemoryError异常。

如何访问内存中的数据?

创建对象一般是new 一个对象,当虚拟机接收到new指令时,作如下操作:

  1. 判断对象对应的类是否加载、链接、初始化
  2. 为对象分配内存
  3. 处理并发安全问题

两种方式解决并发问题

  • 对分配内存空间的动作进行同步处理,比如在虚拟机采用CAS算法并配上失败重试的方式保证更新操作的原子性。
  • 每个线程在Java堆预先分配一小块内存,这块内存称为本地线程分配缓冲(Thread Local Allocation
    Buffer)TLAB,线程需要分配内存时,就在对应线程的TLAB上分配内存,当线程中的TLAB用完并且被分配到新的TLAB时,这时候需要同步锁定。

4.初始化分配到内存空间

5.设置对象的对象头

6.执行初始化方法

对象的堆内存布局

对象在堆内存中的布局分为3个区域,对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)。

对象头:包括Mark World和元数据指针,Mark World用于存储对象运行时的数据,例如HashCode、锁状态标识,GC分代年龄等。元数据指针 用于 指向方法区中目标类的 类型信息,通过元数据指针可以确定具体对象的具体类型

实例数据: 存储对象中的各种类型的字段信息

对齐填充:不一定存在,起到占位符作用,没有特别含义。