Java虚拟机:内存区域分配和对象的创建

Java虚拟机:内存区域分配和对象的创建

虚拟机内存区域分配

首先上一张图:
Java虚拟机:内存区域分配和对象的创建_第1张图片
这里主要是看右边的图,这张图描述了JVM中的内存分配区域,其中蓝色的部分是每个线程独有的,而绿色部分是线程共有的。我们以这张图为准,介绍JVM内存区域分配中的各个区域。

程序计数器

它是程序控制流的指示器,分支,循环,跳转,异常和线程恢复等基础功能都要靠它来实现。简单来说它就是控制程序接下来该干什么事的。可以看到它是线程私有的一片区域,因为多线程之间切换回来之后肯定要恢复原来的运行状态,而每条线程的运行状态都是不一样的,所以需要每一条线程都有一个程序计数器。这个计数器记录的是正在执行的虚拟字节码指令的地址;若是本地方法(Native)的话该指示器的值为0。

虚拟机栈(VM Stack)

虚拟机栈描述的是Java方法执行的线程内存模型:每个Java被执行的时候Java虚拟机栈都会同步创建一个栈帧用于存储局部变量表操作数栈动态链接方法出口等信息。每个方法被调用直至执行完毕的过程就对应着一个栈帧从入栈到出栈的过程。

局部变量表中存放着编译器可知的各种基本数据类型和对象引用,一般是指向对象起始地址的引用指针。这些数据类型在局部变量表中用局部变量槽来表示,其中64位的longdouble会占用两个变量槽,其他的则为一个。而具体一个变量槽到底占多大的空间是有具体的JVM来实现的。

本地方法栈

本地方法栈与虚拟机栈很像,不同的就是一个用来描述Java方法,一个用来描述本地方法,在《Java虚拟机规范》中没有规定该区域的语言,使用方式和数据结构,因此每个虚拟机可以自由实现它。

Java堆区

Java堆是虚拟机管理的内存中最大的一块区域。Java堆是被所有的线程所共享的区域,它在虚拟机启动时创建出来,此区域存在的唯一目的就是存放对象实例,Java世界中几乎所有的对象实例都在这里被分配。Java堆还是由垃圾收集器管理的内存区域,因此又被称为GC堆

如果从内存分配的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区(用来分配对象用的,防止并发不安全的情况),以提升分配时的效率。根据《Java虚拟机规范》的规定,Java堆可以处于物理上不连续的内存空间中,但是在逻辑上它应当被视为连续的。和操作系统的内存分配很类似。

方法区

方法区也是一块线程共享的内存区域,它用于存储已经被虚拟机加载的类型信息常量静态变量即时编译器编译后的代码缓存等数据。它是堆的一个逻辑部分但是本质上又与堆区分开来

垃圾收集行为在这个区域的确是比较少出现,这区域的内存回收的目标主要是针对常量池的回收和对类型的卸载。

运行时常量池

前面说到方法区中要存放常量,这块区域就叫做运行时常量池,它也是方法区的一部分。编译期间生成的各种字面量和符号引用等在加载之后都会被存放进入方法区的运行时常量池中。

对象的创建

类型的加载

那么Java虚拟机接收到一条new指令之后会发生什么呢。首先它将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载。如果没有的话就要先执行类型加载的过程。

一个Java文件被加载到虚拟机内存中到从内存中卸载的过程被称为类的生命周期。其中类的加载包括了三个阶段,分别是加载,链接初始化。首先虚拟机将查找并加载Class文件。根据《Java虚拟机规范》的描述,加载阶段主要做了三件事情:

  • 根据特定名称查找类或者是接口类型的二进制字节流。
  • 将这个二进制字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  • 在内存中生成一个代表这个类的Class对象,作为方法区这个类的各种数据的访问入口。

链接过程又可以被分为三个阶段:

    1. 验证:确保被导入类型的正确性。
    1. 准备:为类的静态字段分配字段,并用默认值初始化这些字段。
    1. 解析:虚拟机将常量池内的符号引用替换为直接引用。

链接过程结束之后就进入到初始化阶段,这个阶段中会将该类的变量初始化为正确的值

Java虚拟机:内存区域分配和对象的创建_第2张图片

内存分配

类型加载完毕之后就是对象实例的内存分配了,在之前的内存区域分配中我们已经知道对象实例的内存实际上是被分配在Java堆区域之中的,既然要分配内存的话肯定是使用还未被分配的内存。为了区分哪些内存已经被分配,哪些还没有被分配就有许多策略,第一种策略便是指针碰撞策略:所有被使用的内存被放在一边,没有被使用过的在另一边,中间有一个指针指向分界线,分配内存时只需要将指针移动即可。
Java虚拟机:内存区域分配和对象的创建_第3张图片

如果内存空间不是规整的话,那么虚拟机就不能使用指针碰撞了,这个时候虚拟机需要维护一个列表并记录哪些内存是可用的,分配时在这个表中查找到足够大的空间分配给新对象并更新列表,这种策略被称为空闲列表

除此之外还有一个问题就是一个对象被分配之后指针还没有来得及修改另一个对象紧接着就又被分配了,这种情况下就会产生前一个对象被覆盖的问题,为了解决该问题也有两种策略:一种就是将内存分配的行为进行同步,保证分配对象这整个指令原子性的;另一种方法就是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一块内存,称之为**本地线程分配缓冲区。**只有这块缓冲区用完之后才要进行同步。内存分配完之后会将分配到的内存空间都初始化为0。

最后虚拟机还需要对对象进行必要的设置,例如说这个对象是哪个类的实例,如何才能找到类的元数据信息等。这些信息是存放在对象的对象头(Object Header)中的。

初始化

这一切工作都完成之后从虚拟机的角度来说这个对象已经被创建出来了,接下来要执行的Class文件中的方法了,也就是构造函数中的初始化部分。

你可能感兴趣的:(Java学习笔记,java,开发语言,jvm)