一、JVM运行时数据区
JVM运行时数据区是一个抽象概念,主要依赖于寄存器、高速缓存、主内存几个部分组成。
计算机运行 = 指令 + 数据,指令用于执行 方法, 数据 用于指向 存放的数据和对象。
虚拟机栈 --- 用于执行java方法
本地方法栈 --- 执行本地方法(通常时c语言实现的)
程序计数器 --- 用于对 执行程序的计数
java中的数据 --- 主要有 变量、常量、对象、数组等相关数据
1.1 线程私有的区域
程序计数器
占用内存空间较小,当前线程执行 java字节码的行号指示器;各线程之间独立存储,互不影响和干涉。
如果当前线程正在执行的是 java方法 ,则指明当前线程 执行的 代码字节码行数。
如果当前线程正在执行的是 native方法,则这个计数器值为空(undefined)。
此内存区域是唯一一个不会出现outofMemoryError情况的区域。
虚拟机栈
每个线程在运行时,在执行每个方法的时候都会打包成一个栈帧,存储了局部变量表,操作数栈,动态链接,方法出口等信息,然后放入栈。每个时刻正在执行的当前方法就是虚拟机栈顶的栈桢。方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程。
栈的大小缺省为 1M,可用jvm参数 –Xss 调整大小,例如-Xss256k
局部变量表:顾名思义就是局部变量的表,用于存放我们的局部变量的。
操作数据栈:存放我们方法执行的操作数的一个栈,遵循先进后出原则。
动态连接:因为有它,所以使 Java 语言具有多态特性
返回地址:
正常返回:(调用程序计数器中的地址作为返回)
三步曲:
1.恢复上层方法的局部变量表和操作数栈 为 默认值、
2.把返回值(如果有的话)压入调用者栈帧的操作数栈中、
3.调整 PC 计数器的值以指向方法调用指令后面的一条指令、
出现的异常:
线程请求的栈深度大于虚拟机所允许的深度:StackOverflowError
JVM 动态扩展时无法申请到足够的内存时:OutOfMemoryError
本地方法栈
本地方法栈 native 方法调用JNI 到了底层的c/c++汇编语言,然后调用系统内核,驱动系统运转操作。
1.2 线程共享的区域
1.2.1 方法区/或永久代
用于存储已经被虚拟机加载的类信息,常量("zdy","123"等),静态变量(static 变量)等数据
可用以下参数调整
jdk1.7 及以前:-XX:PermSize;-XX:MaxPermSize;
jdk1.8 以后:-XX:MetaspaceSize; -XX:MaxM
类信息包括:
类的完整有效名称、返回类型、修饰符(public/private/protected...)、变量名、方法名、方法代码、这个类的直接父类的完整有效名、类的直接接口的有序列表。
运行时常量池
1.符号引用
比如 类加载器装载 一个类时,此时可以通过虚拟机获取这个类的实际内存地址,再通过该类的全限定名 作为引用符号来替换这个类的实际内存地址,在jvm编译成字节码时,用引用符号来来替换引用地址,再在加载类时再通过虚拟机获取该引用符号指向引用内的实际内存地址。
2.字面量
文本字符串 String a = "abc",这个 abc 就是字面量
八种基本类型 int a = 1; 这个 1 就是字面量
说白了就是引用符号的值。
1.2.2 堆
几乎所有对象都分配在这里,也是垃圾回收发生的主要区域,
可用以下参数调整:
-Xms:堆的最小值;
-Xmx:堆的最大值;
-Xmn:新生代的大小;
-XX:NewSize;新生代最小值;
-XX:MaxNewSize:新生代最大值;
例如- Xmx256m
1.2.3 直接内存
直接内存也属于线程共享的区域。
使用 Native 函数库直接分配堆外内存(NIO)
并不是 JVM 运行时数据区域的一部分,但是会被频繁使用(可以通过-XX:MaxDirectMemorySize 来设置(不设置的话默认与堆内存最大值
一样,也会出现 OOM 异常)
使用直接内存避免了在 Java 堆和 Native 堆中来回复制数据,能够提高效率
测试用例 JavaStack:设置 JVM 参数-Xmx100m,运行异常,因为如果没设置-XX:MaxDirectMemorySize,则默认与-Xmx 参数值相同为 100M,
分配 128M 直接内存超出限制范围
站在线程角度来看
线程私有区域:虚拟机栈、本地方法栈、程序计数器三个区域的生命周期和线程相同。
线程共享区域:涉及到生命周期管理和垃圾回收等概念