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.jar03、Java虚拟机栈(线程栈)
其实就是开发过程中:
执行某个方法比如:main启动执行
比如你在浏览器发起一个请求,http://localhost:8080/product/list ,springmvc执行某个方法
等,执行执行某个方法,或者发起请求执行方法,在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