JVM底层原理学习(三)之JVM内存区域

上一篇主要学习了类加载子系统,这一篇学习JVM的运行时数据区

上一篇 JVM底层原理学习(二)之类加载子系统

运行时数据区(Run-Time Data Areas)

JVM的设计者将不同的类型的内容存储在JVM规划的不同区域.这些区域总结起来就是运行时数据区(Run-Time Data Areas)如下图。

The Java Virtual Machine defines various run-time
data areas that are used during execution of a
program. Some of these data areas are created on
Java Virtual Machine start-up and are destroyed only
when the Java Virtual Machine exits. Other data
areas are per thread. Per-thread data areas are
created when a thread is created and destroyed when
the thread exits.
译:
Java虚拟机定义了在程序执行期间使用的各种运行时数据区域。
其中一些数据区域是在Java虚拟机启动时创建的,只有在Java虚拟机
退出时才会被销毁。有的数据区域是线程独享的,数据区域在创建线程
时创建,在线程退出时销毁。

运行时数据区
在这里插入图片描述

Method Area(方法区)

  • 方法区是各个线程共享的内存区域,在虚拟机启动时创建
  • 存储的是不经常变动的数据内容.基本不会发生频繁的GC
  • 虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却又一个别名叫做Non-Heap(非堆),目的是与Java堆区分开来。从而单独对堆区进行垃圾回收。
  • 用于存储已被虚拟机加载的类信息常量静态变量(JDK1.6)、 即时编译器编译后的代码等内容
    • 类型信息(类元信息): (类Class,接口interface,枚举Enum,注解 annotation)
      • 这个类的完整有效名称(全名=包名.类名)
      • 这个类型直接父类的完整有效名,对于interface或Object没有父类
      • 这个类型的修饰特征符,public, abstract,final —> 2的N次幂来表示
    • 字段信息(域信息)
      • JVM必须在方法区中保存类型的定义的所有域的相关信息,以及域的声明顺序
      • 域的相关信息包括:域名称、域类型、域修饰特政符
    • 方法信息
      • 这个方法的方法名称
      • 方法的修饰特征 public, abstract,final —> 2的N次幂来表示
      • 方法的返回类型(包含void)
      • 方法的参数列表(类型+数量)
      • 方法的字节码指令(bytecodes),操作数栈,局部变量表的情况
    • 运行时常量池
      在字节码文件中,包含了常量池.在运行时将字节码文件中的 常量池加载到方法区,就是运行时常量池
      • stringtable字符串常量池在JDK1.6版本中字符串常量池存储在方法区.在JDK1.7,JDK1.8移至了heap中
    • 静态变量(non-static变量)
      JDK1.6版本,静态变量存储在方法区.在JDK1.7,JDK1.8 移至了heap中
    • JIT即时编译代码
  • 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
  • 方法区是一个定义(标准),不同版本的实现是不同的;
    1.7之前方法区的实现叫Perm space(JVM直接控制)
    1.8之后叫meta space(操作系统的直接内存JVM并无法直接进行监控和控制.)
  • 方法区在JVM启动的时候被创建,被所有线程共享,其生命周期与JVM虚拟机一致

JDK1.6版本方法区存储详情:

在这里插入图片描述

JDK1.7版本方法区存储详情

在这里插入图片描述

JDK1.8版本方法区存储详情:

在这里插入图片描述

运行时常量池

Each run-time constant pool is allocated from the Java Virtual Machine's method area
译:
运行时常量池是分配至方法区的. 即 运行时常量池属于方法区的一部分.(jdk1.6之前属于方法区,1.7之后属于堆区)

即字节码文件中的常量,在运行阶段被加载至运行时常量池

Heap(堆)

  • 堆是Java虚拟机所管理的共享内存中最大的一块,在虚拟机启动时创建,被所有线程共享。
  • **“几乎”**所有Java对象实例以及数组都在堆上分配
  • 堆是GC执行垃圾回收的重点关注对象.
    堆空间示意图:
    在这里插入图片描述
  • 为什么将分为Method Area 和 Heap 两部分?

Method Area存储的是不经常变动的数据内容.基本不会发生频繁的GC
工作.如 类信息、方法信息、字段信息、jit即时编译的代码
Heap中存储的是运行时产生的数据对象.针对这些内容会频繁进行内存

  • 为什么将堆分为Old 和 Young 两部分?

堆是我们JAVA运行过程中最主要的共享工作内存,JVM机制是共享内存垃圾自动管理的虚拟机…如果我们的每一次对象回收都要扫描整个堆区.将带来很大的工作量和性能损耗.

  • 为什么需要将young区分为Eden ,S0 , S1三个区域.

当young区的内存占用到达一定的量时,如果此时进行直接清理. 带来的是空间碎片的问题…另外加上young区的对象一般都是朝生夕 死.所以大部分的内容都会被清理.存活的对象就放入s0或者s1.有助于垃圾回收和清理.

  • 新生代中Eden:S0:S1的比例默认为什么是8:1:1?

IBM公司的专门研究表明,新生代中的对象大概98%是“朝生夕死” 8:1:1是基于大量实验和数据收集分析统计对比之后的比较合理的比例

内存的异常

  • Heap异常OutOfMemoryError
    调整程序运行参数. -Xmx20M -Xms20M
  • Method Area异常OutOfMemoryError
    调整参数 -XX:MetaspaceSize=30M -XX:MaxMetaspaceSize=30M

Java对象的布局

既然heap中存储的内容是Java的对象.那下面我们一起来了解一下对象在我们的共享内存中是如何布局的.

在这里插入图片描述
一个Java对象在共享内存中会包括3个部分:对象头、实例数据和对齐填充
如果是数组对象将在对象头中多一个数组长度的length

对象头
  • markoop(markword) 8个字节
  • klassOop(Class pointer) 8个字节 若启动压缩指针(4个字节)
  • length 4字节
对象实际数据
  • boolean 和 byte 1字节
  • short 和 char 2字节
  • int 和 float 4字节
  • long 和 double 8字节
  • reference 8字节
对齐填充(可能存在)

确保对象的大小为8字节的整数倍
在这里插入图片描述
Java对象的一生
一般情况下,新创建的对象都会被分配到Eden区,一些特殊的大的 对象会直接分配到Old区。

我是一个普通的java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden
区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从
去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定
所。直到我15岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代
里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加
一岁),然后被回收。

对象从出生到回收的流程图:
在这里插入图片描述

Java对象真的是这样的吗?
栈上分配
  • 为什么需要栈上分配
    在JAVA程序运行过程中,其实有很多的引用类型的对象作用域都不会逃逸到方法之外,即该对象的生命周期是跟方法一致的.对于这样类型的对 象,我们是否一定要考虑将对象不在分配在堆空间中吗?如果这
    样,在线程结束之后,该对象会成为Heap空间的垃圾,带来GC的性能消耗.
    所以针对不会逃逸出方法的对象.JVM允许将对象(聚合量)属性打散后分配在栈(线程私有的,属于栈内存)上这种方式就叫做栈上分配
    栈上逃逸对象,如图
    在这里插入图片描述

  • 栈上分配如何开启
    -XX:+DoEscapeAnalysis
    -server

  • 开启对象逃逸分析的优势

    • 同步锁消除
      在这里插入图片描述

    • 标量替换
      标量(scalar replacement):就是不能再被分解的量。
      (八大基本类型 byte , short , int ,long ,char , float ,double
      , boolean)另外指向对象的引用也是标量。 聚合量
      (aggregate)就是还能继续被分解的量,例如对象能被分解成多个标量。
      如果把一个Java对象拆散,将其成员变量恢复为分散的变量,这就叫做标量替换。拆散后的变量便可以被单独分析与优化,可以各自分别在活动记录(栈帧或寄存器)上分配空间;原本的对象就无需整体分配空间了

JVM
-XX:+EliminateAllocations	可以开启标量替换
-XX:+PrintEliminateAllocations	查看标量替换情况
线程本地缓冲区 TLAB(thread-local allocation blocks)

前面的内容我们已知常规场景下对象分配在heap-Young-Eden上, 而堆是一个全局共享的区域,当多个线程同一时刻操作堆内存分配对象空间时,就需要进行同步或者说是串行的操作.不要定会带来同一个空间争夺的并发问题.而采用同步(串行)带来对象分配定会让其分配效率变差(尽管JVM使用CAS处理分配失败).

TLAB就是解决这个问题的设计.在Eden区当一个线程启动时开辟每一个线程私有的很小的缓冲空间.后续线程需要创建对象只要TLAB 空间能放下就会在此空间进行创建.避免同步(串行),提升对象分配的效率.

-XX:+UseTLAB
-XX:TLABSize=512K

对象的分配过程

在这里插入图片描述
面试题

  • 栈指向堆
public static void main(String[] args){ 
	Object obj = new Object();
}
  • 方法区指向堆
public static Object obj = new Object();	// JDK1.6版本
  • 堆指向方法区
    对象布局中在markword 中存在有class point
  • 堆指向栈
    对象的轻量级锁,对象头信息指向栈空间的lock record

Java Virtual Machine Stacks(虚拟机栈)

  • 虚拟机栈是当前执行线程独占空间.以栈的数据结构形式存在;
  • 虚拟机栈是线程执行的区域,它保存着一个线程中方法的调用状态。换句话说,一个Java线程的运行状态,由当前线程挂钩的虚拟机栈来保存;
  • 每一个被线程执行的方法,为该栈中的栈帧,即每个方法对应一个栈帧。调用一个方法,就会向栈中压入一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出

栈帧

栈帧的生命周期与方法的调用相关.每个栈帧对应一个被调用的方法,可以理解为一个方法的运行空间

  • 栈帧是随方法被调用来创建,随着方法的结束而销毁.结束不管是正常的结束还是异常的结束.
    栈帧的示意图:
    在这里插入图片描述
  • 方法返回地址(Normal Method Invocation Completion和 Abrupt Method Invocation Completion):方法返回地址:当一个方法开始执行后,只有两种方式可以退出,一种是 遇到方法返回的字节码指令;一种是遇见异常,并且这个异常没有在方法体内得到处理。或者理解为方法执行完的出口
  • 运行时动态链接(Dynamic Linking):每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。
    在class中都是符号引用…method_ref也是一样, 但是在运行或者说调用阶段我们必须知道我们知道方法的具体执行版 本.所以动态链接就是指向具体方法区的方法引用.因为方法的确认必须 在运行阶段才能指定,所以叫动态链接.
  • 局部变量表(Local Variables):方法中定义的局部变量以及方法的参数存放在这张表中局部变量表中的变量不可直接使用,如需要使用的话,必须通过相关指令(复制)将其加载至操作数栈中作为操作数使用。
  • 方法局部变量表在静态方法中只有3个局部变量. 在实例方法中有4个局部变量;

在实例方法中,局部变量表中的下标从1开始.因为0号位置默认为
this对象.静态方法则不然.

  • 操作数栈(Operand Stack):栈的数据结构.以压栈和出栈的方式存储操作数的数值
    知识点:
    为什么要设计操作数栈
    JVM的指令集架构是基于栈的指令集架构
    虚拟机的指令集架构方式还有一种指令集架构是基于寄存器的指令集架构
    在这里插入图片描述

栈式指令集架构(JVM)

  • 指令大部分是零地址指令,其执行过程完全依赖于Java虚拟机栈 中的操作数栈
    指令一般包含两大部分组成: 操作码 和 操作数
    零地址指令只有操作码,没有操作数。这种指令有两种情况:一是无需操作数,另一种是操作数为默认的(隐含的)
  • 由于是零地址指令,生成的指令空间占用更少
  • 不受限于物理的硬件资源,可移植性强,更好的实现跨平台性

寄存器指令集架构(Davlik)

  • 指令采用一地址指令,二地址指令,三地址指令.其执行过程依赖于硬件的寄存器
  • 指令空间占用更多,但是完成的功能可以更复杂(花更少的指令完成更复杂的操作)
  • 性能更为优秀,执行更为高效.
  • 指令集依赖硬件资源,可移植性较大限制

案例(方法扭转)

在这里插入图片描述

  • iconst_3 :将3的int型常量压如操作数栈顶
  • istore_0:将操作数栈顶的int类型的值赋值给index为0的本地变量表中的变量
  • i_load_1:把本地变量表中的index为1的int类型值压入到操作数栈顶
  • iadd:将操作数栈顶的两个int类型的值进行相加操作
  • i_load_2:将本地变量表中index为2的变量的int类型的值压入到操作数栈顶
  • ireturn:将操作数栈顶的int类型值作为方法的返回

栈异常

在java中不合理的递归一定会是造成java虚拟机栈溢出

public class StackOverflowDemo {
    private static int count=0;
    public static void method1(){
		count++;
		System.out.println("stack count: "+count); method1();
    }
    public static void main(String[] args) {
		method1(); }
	}

运行之后可以打印栈的深度,最终报出stackoverflowExecption
可通过 -Xss256k 调整虚拟机栈空间大小

The pc Register(程序计数器)

我们都知道程序的逻辑执行一定是基于最小的执行单元线程来执行的.

一个JVM进程中必然有多个线程在执行,而线程中的内容是否能够拥有执行权,是根据CPU调度来的。

假如线程A正在执行到某个地方,突然失去了CPU的执行权,切换 到线程B了,然后当线程A再获得CPU执行权的时候,怎么能保持此前状态和逻辑继续执行呢?这就是需要在线程中维护当前执行的状态记录保持,记录线程执行到的位置,以便于下次拿到CPU执行权时 基于此状态恢复线程的执行.所以The pc Register 就是干这个事情的.

小知识点:

  • 如果线程执行的Java方法,则计数器记录正在执行的虚拟机字节码的指令的地址
  • 如果正在执行的本地方法,这个计数器值则是(undefined)
  • 运行时数据区中唯一不会出现OOM的区域,没有垃圾回收
  • 每个线程有一个独立的程序计数器,线程之间互不影响

Native Method Stacks(本地方法栈)

在JDK源码中很多的Native方法.比如hashcode…这些方法都是
C/C++的代码实现的逻辑.

在java的运行过程中,我们经常会调用到C的本地方法来完成功能实现. 针对C/C++代码实现的方法(也就是这些native修饰的方法)调用过程.需要有一个本地方法栈来保存方法的执行操作逻辑和操作数据.本地方法栈就是这样的一个角色.

执行引擎(Execution Engine)

官网架构图
在这里插入图片描述

问:JAVA到底是解释型语言还是编译型语言?
javac是java的前置编译器,他的主要职责是将java源文件编译成class文件.
在jvm虚拟机中运行过程中,还需要将class文件解析机器能识别的机器码.所以在jvm的执行引擎中存在解释器.
所以java语言兼顾编译型和解释型的特点.

在这里插入图片描述

mixed mode ---> 指的jvm的执行class过程,存在解释执行和即时编译的即时编译器.

-Xint	-Xcomp	参数指定方式进行编译或者解释执行.

执行引擎图示
在这里插入图片描述

解释器(interpreter)

将class字节码文件逐行翻译成机器码并马上交给CPU执行

即时编译器(JIT Compiler)

在运行过程中,进行热点代码的侦测.将热点代码进行预编译操作.并将预编译的代码存储在方法区.在热点代码再次运行的过程中直接使 用编译之后的机器码指令即可.无需再次解释或编译.

垃圾回收器(Garbage Collection)

Java语言的风靡原因中有一个非常重要的原因,即对共享内存的管理. 对于共享对象的回收机制.以前的C/C++代码我们工程师需要对共享 的对象进行内存的管理.现在JVM的垃圾回收器就自动地帮我们打理 了对象的生死存亡.垃圾回收机制针对的区域 method ares & heap

JVM概览图

在这里插入图片描述
下一篇:JVM底层原理学习(四)之垃圾回收

猜你喜欢

转载自blog.csdn.net/nonage_bread/article/details/108065454