深入学习java虚拟机第二章

自动内存管理机制

  • 一、运行时数据区域
    • 1. 程序计数器:
    • 2. 虚拟机栈:
    • 3.本地方法栈:
    • 4.堆(GC):
    • 5.方法区:
  • 二、HotSpot虚拟机对象探秘
    • 1.对象的创建
      • (1)类加载检查:检查常量池中是否存在该类
      • (2)分配对象的内存
      • (3)对象初值初始化(default值的赋值)
      • (4)写对象的状态信息
      • (5)对象赋予程序规定的值
    • 2.对象的内存布局
    • 3.对象的访问定位
    • 4.虚拟机中的内存溢出

一、运行时数据区域

运行时数据区图

深入学习java虚拟机第二章_第1张图片


1. 程序计数器:

存储线程指令地址位置。线程私有的

2. 虚拟机栈:

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

3.本地方法栈:

和虚拟机栈功能相似,不同点是执行的是native方法。

4.堆(GC):

java虚拟机内存管理中最大的一块,被所有线程共享的一个区域,在虚拟机启动的时候创建。堆的作用是存放对象实例,几乎所有的对象实例都会在这里分配内存。

5.方法区:

也是线程共享的内存区域,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。

  • 运行时常量池:方法区的一部分,根据字面意思,就是存放各种字面量和符号引用。他的一个特点就是具备动态性:常量并不是只有编译的时候才会产生,在程序运行过程中也可能会放入新的常量进入常量池中。

二、HotSpot虚拟机对象探秘

1.对象的创建

(1)类加载检查:检查常量池中是否存在该类

虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

(2)分配对象的内存

在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。当内存中分配的单元连续时,采用“指针碰撞”(Bump the Pointer)方式。当内存中分配的单元不连续的时候,采用的是“空闲列表”(Free List)方式。选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

  • 可能产生的问题:指针在进行创建对象的时候,由于并发导致程序出错。存在两种解决方法
    1. 使用同步处理,采用CAS配上失败重试的方式保证更新操作的原子性
    2. 将内存分配的动作按照线程分配到不同的空间中,每个空间互不影响,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB,只有当空间用完之后,才会采用同步的方式对其空间扩容。

(3)对象初值初始化(default值的赋值)

内存分配完成之后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),如果使用TLAB,这一工作过程也可以提前至TLAB分配时进行。这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

相当于赋予属性初值的操作,比如说int默认值为0,对象类型默认值为null

(4)写对象的状态信息

接下来虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。存放在对象头 Object Header之中。

(5)对象赋予程序规定的值

在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从Java程序的视角来看,对象创建才刚刚开始——<init>方法还没有执行,所有的字段都还为零。所以,一般来说(由字节码中是否跟随invokespecial指令所决定),执行new指令之后会接着执行<init>方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来

2.对象的内存布局

在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)

  • 对象头:又分为了两部分,一部分用来存对象的状态信息,例如:hashcode,GC分代的年龄,锁标志等。另一部分是类型指针,用来指定当前对象的类,虚拟机用来确定这个对象是哪个类的实例。

如果对象是一个java数组,将额外存储一个数组长度的数据

  • 实例数据:对象真正存储的有效信息,各种类型的字段内容都存在这里。
  • 对其填充:并不是必然存在,无特别含义,为了内存管理为8字节的整数倍

3.对象的访问定位

建立对象是为了使用对象,我们的Java程序需要通过栈上的reference数据来操作堆上的具体对象。有句柄直接指针两种方式

  • 通过句柄访问对象
    深入学习java虚拟机第二章_第2张图片
    如果使用句柄访问的话,那么Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
  • 通过直接指针访问对象
    深入学习java虚拟机第二章_第3张图片
    如果使用直接指针访问,那么Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。

在对象的内存布局中,就需要存储一个对象类型数据,使得指针访问到的对象数据里面可以直接定位到对象类型数据。

两种方式的优缺点:这两种对象访问方式各有优势,使用句柄来访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。
使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。就本书讨论的主要虚拟机Sun HotSpot而言,它是使用第二种方式进行对象访问的,但从整个软件开发的范围来看,各种语言和框架使用句柄来访问的情况也十分常见。

4.虚拟机中的内存溢出

  • 堆内存溢出:当堆中的对象数据大小超过堆容量大小时,将发生
  • 虚拟栈和本地方法溢出:当栈深度超出虚拟栈栈规定深度时候会抛出StackOverflowError异常,在扩展栈时申请不到足够的内存空间,则会抛出OutOfMemoryError异常。
  • 方法区和运行常量池溢出:

你可能感兴趣的:(JVM笔记,jvm,java)