JVM运行时数据区

01、JVM运行时数据区

构成:

线程的共享区:堆,方法区

线程的非共享区:Java虚拟机栈(线程栈)、本地方法栈、程序计数器

02、方法区

也被称为永久代,在jdk1.8中称之为:元数据空间,是线程共享的区域。存储已被虚拟机加载的**类信息、常量、静态变量、即使编译器编译后的代码等数据**。方法区无法满足内存分配需求时,抛出OutOfMemoryError。JVM规范被没要求这个区域需要实现垃圾收集,因为这个区域回收主要针对的是类和常量池的信息回收,回收结果往往难以令人满意。

运行时常量池:是方法区的一部分。Java语言不要求常量只能在编译期产生,换言之,在运行期间也能将新的常量放入。

方法区空间大小设置: -XX:PermSize -XX:MaxPermSize
1.8之后设置: -XX:MetaspaceSize  -XX:MaxMetaspaceSize

如何进行配置

java -jar  -XX:MetaspaceSize=128m  -XX:MaxMetaspaceSize=256m xxx.jar

03、Java虚拟机栈(线程栈)

其实就是开发过程中:

等,执行执行某个方法,或者发起请求执行方法,在jvm中都是一次栈调用的过程。字节引擎会开启对应的线程去执行方法,把某个方法从通过动态连接从方法区中对应的类方法信息取出,形成一个栈帧,然后入栈执行的方法。

它会把线程执行的方法在虚拟机栈申请一块空间出来,然后把整个方法形成一个栈帧,而这个栈帧中包含如下几个部分:

何谓栈帧:一个方法在线程栈中对应的一块内存区域。就称之为栈帧。

栈帧包括几个部分:

  • 局部变量表:存储方法的局部变量的存储结构

  • 操作数栈:提供方法在运算过程中的操作数的临时的存储结构。比如:+ - * / 等。

  • 方法入口信息(动态链接):把符号引用转换成直接引用, 比如在执行方法obj.sum()的过程中,我们需要找到方法代码存储的位置,我们知道方法的代码存储在方法区中的类信息中,如果找到这块区域,就是通过动态链接获取,动态链接会把方法区中的代码取出压栈到栈帧中,然后提供给线程执行。

  • 方法出口信息:记录方法执行结束的位置,并且保存线程的生命状态,然后如果栈还有栈帧就继续执行,直到结束位置。

Compiled from "TestMath.java"
public class com.kuang.vo.TestMath {
  public static final int num;
​
  public static com.kuang.entity.User user;
​
  public com.kuang.vo.TestMath();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
​
  public int sum();
    Code:
       0: iconst_1 --- 将将int型(1)推送至栈顶 -- 
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: bipush        10
       9: imul
      10: istore_3
      11: iload_3
      12: ireturn
​
  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class com/kuang/vo/TestMath
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #4                  // Method sum:()I
      12: pop
      13: return
​
  static {};
    Code:
       0: new           #5                  // class com/kuang/entity/User
       3: dup
       4: invokespecial #6                  // Method com/kuang/entity/User."<init>":()V
       7: putstatic     #7                  // Field user:Lcom/kuang/entity/User;
      10: return
}
​

04、程序计数器

概述 相信学过计算机组成原理的人都知道,CPU内部的寄存器中就包含一个程序计数器,存放程序执行的下一条指令地址。 程序每次执行时,CPU都会自动存储下一条的指令的地址,以便下次继续执行

Jvm偷学了CPU JVM也是参考了CPU的程序计数器,自己设计了一个程序计算器。用于存储JVM当前执行bytecode的地址(节码指令地址)

程序计算器:记录当前正在执行的字节码地址。 程序计数器是线程隔离的:每一个线程在工作的时候都有一个独立的计数器

认识

执行方法,其实确实线程在执行调度,但是真正通知做事的CPU。CPU大脑,它不停的分配时间片段给的每个线程。

问题:

我们执行方法,可能会消耗时间,这个如果cpu轮询到某个线程,就执行方法,但是cpu给你时间时间片段是有限的。为什么要这样做呢?因为考虑一个雨露均沾的问题,让每个线程执行都有机会。

如果一个方法在执行过程中,如果执行了一半或者没有执行完毕,就到了cpu给你时间,如果cpu再次给你时间执行的片段。是从新开始执行还是续弦。答案肯定是:续弦。

java程序计数器:就可以去完成这件事情。它其实就是去记录每个线程执行方法的位置。当方法没有执行完毕,cpu下次再次轮询到该线程的时候,把当前程序计数器的位置,接着继续执行,知道执行完毕然后在释放。

程序计数器和线程栈的关系

当在调用方法的时候,发起请求的时候,jvm字节执行引擎会开启一个线程去执行方法,把方法包裹一个栈帧然后在java虚拟机栈(线程栈)中挖一个空间出来,然后入栈执行,同时会为没个栈帧都开启一个程序计数器,以便于记录程序执行位置,然后cpu轮询到该线程的时候继续去执行。

程序计数器记录的是什么?

字节指令地址

05、什么是字节码指令地址

字节指令地址:其实就给程序计数器去记录索引位置。

为什么要存储这个字节码指令地址?

由于CPU的上下文切换,导致不停的切换每条线程,就会出现线程经常中断和恢复。 1、当CPU上下文切换,线程中断时,必须准确记录每条线程的执行位置。即记录当前线程执行的字节码每条线程的执行位置,即记录当前线程执行的字节码指令地址。 2、当线程恢复时、再从中断的位置继续执行。 3、为了达到这样的一个效果,JVM为每条线程都分配了一个程序计数器。 4、这样每条线程都可以独立计算,故当出现CPU上下文切换时,线程中断,程序计数器记录了当前的字节码指令位置,线程恢复,就从中断的位置继续执行

比如:

CPU并发两条线程:

1、CPU首先执行线程A,假设线程A执行到第3行时,CPU突然切换到执行线程B。那么线程A就处于中断状态,线程A的计数器记录执行的位置4。

2、当线程B执行到第10行的时候,CPU突然切换执行到线程A,同理中断线程B,并线程B记录下一个字节码地址11。被切换的线程A恢复后,从字节码地址4开始执行。

由此可见:如果没有程序计数器的话,CPU上下文切换时,多线程计算必定乱成一锅粥。

06、程序计数器的面试题(重点)

1、简单的说下什么是程序计数器?

程序计算器:记录当前正在执行的字节码地址。 程序计数器是线程隔离的:每一个线程在工作的时候都有一个独立的计数器

2、程序计数器为什么要存储字节码指令地址呢?

由于CPU的上下文切换,导致不停的切换每条线程,就会出现线程经常中断和恢复。 1、当CPU上下文切换,线程中断时,必须准确记录每条线程的执行位置。即记录当前线程执行的字节码每条线程的执行位置,即记录当前线程执行的字节码指令地址。 2、当线程恢复时、再从中断的位置继续执行。 3、为了达到这样的一个效果,JVM为每条线程都分配了一个程序计数器。 4、这样每条线程都可以独立计算,故当出现CPU上下文切换时,线程中断,程序计数器记录了当前的字节码指令位置,线程恢复,就从中断的位置继续执行

3、为什么程序计数器具有线程隔离性?

就是每条线程都有有一个计数器,其目的是为了保证每个线程执行过程中不冲突,以及每个线程执行的位置。

4、为什么程序计数器是java虚拟机规范中唯一一个没有规定任何OutofMemeryError的区域?

程序计数器:占用的内存空间非常的小,可以忽略不计。

Comments