目录
一、JVM是一种规范
1.1 Java程序的执行过程
.java文件首先被javac编译成.class(字节码文件),然后通过JVM的执行引擎把字节码解释为对应的机器码指令,再通过机器码指令调用硬件。
.java---javac--->.class---jvm---机器指令
JVM中有两种执行引擎:字节码解释器和JIT编译器
1.2 JVM的特点
特点:跨平台 跨语言
跨平台:我们写一个类在不同的操作系统(Linux、Windows、Macos)上能够表现一致;为了实现跨平台性,不同操作系统有对应的JDK版本Java SE Development Kit 8u391
跨语言(语言无关性):JVM只识别.class文件 只要编译出来的class符合JVM规范,都可以在JVM上跑,跨平台性为Java生态圈的强大奠定了基础。
二、JVM的内存区域
2.1 *运行时数据区
运行时数据区定义:Java虚拟机会在执行Java程序的时候把它管理的数据区域划分为不同的数据区,在JVM中,内存主要分为方法区、堆、虚拟机栈和本地方法栈等。该部分内存是把真实的内存虚拟化。
按线程共享、私有和直接内存可以对内存区域分类
- 线程私有区域:一个线程拥有单独的一份内存区域。包括虚拟机栈、本地方法栈和程序计数器
- 线程共享区域:被所有线程共享,有且只有一份,包括方法区和堆
- 直接内存(堆外内存):假如系统有6G内存,被JVM虚拟化了5G内存,那么还剩的1G就是直接内存,这部分内存使用起来较麻烦,且没有被虚拟化
如果我们执行如下代码
public class ObjectAndClass {
static int age=18;//todo 静态变量(基本数据类型)
final static int sex=1;//todo 常量(基本数据类型)
final static ObjectAndClass object = new ObjectAndClass();//todo 成员变量指向(对象)在类加载的时候不会执行
//构造方法 -》ObjectAndClass object = new ObjectAndClass();
private boolean isKing;//todo 成员变量 放在哪里???
public static void main(String[] args) {//启动一个线程,创建一个虚拟机栈,数据结构,单个,压入一个栈帧
int x=18;//todo 局部变量(基本数据类型)
long y=1;//todo 局部变量(基本数据类型)
ObjectAndClass lobject = new ObjectAndClass();//todo 局部变量 引用 (对象)
lobject.isKing=true;// isKing跟随对象,堆空间
lobject.hashCode();//方法中调用方法 本地方法(C++语言写 JNI)
ByteBuffer bb = ByteBuffer.allocateDirect(128*1024*1024);//todo 直接分配128M的直接内存
//这个地方 分配在哪里 128M
}
}
则各个变量的分配入下图:
类class、静态变量(基本数据类型)、常量分配在方法区;
对象的分配在堆中;
对象的引用、方法中的局部变量在栈帧的局部变量表中
三、线程共享区
3.1 方法区
运行时线程共享的区域。存放了静态变量、常量、类
3.2 *堆
JVM中最大的一块儿内存,我们常说的GC就发生在这个区域。对象一般都在这个区域,Java中的对象分为基本数据类型(byte、short、int、long、float、double、char)和普通对象,如果是普通对象,在堆中创建,然后在其他地方引用,比如栈帧中的局部变量表。如果是基本数据类型,如果在方法体内声明,则在栈中分配内存,其他的情况都在堆中。
堆大小参数:-Xms :堆的最小值;-Xmx :堆的最大值;-Xmn :新生代的大小;-XX:NewSize ;新生代最小值;-XX:MaxNewSize :新生代最大值
四、线程私有区
4.1 *虚拟机栈
定义
用来存放线程执行Java方法所需的数据、指令和返回地址。每个线程私有,每执行一个方法,都会生成一个栈帧压入虚拟机栈中,一旦方法完成调用,则会出栈。方法的执行就对应着栈帧的入栈和出栈过程。如果方法死递归。则会造成栈溢出,默认是1M。
构成:虚拟机栈由栈帧构成,每个方法执行会生产栈帧压入栈中,栈顶的栈帧是当前执行的方法。栈帧由操作数栈、局部变量表、动态连接和完成出口构成。
局部变量表:是一张表,存储方法执行过程中的局部变量(Java八大基本数据类型)、对象的引用和return返回值。如果是对象,只存了对象的引用地址。还会存储returnAddress类型,即返回值。
操作数栈:就是一个栈,用于存储和操作方法执行过程中需要的数据值,里面可以完成基本数据类型的存取、运算和类型转化,存储包括基本数据类型、布尔值和对象引用等。
动态连接:跟多态有关
完成出口:记录方法跳入的地址,调用位置,方法执行完出去,接着跳入的地址继续执行。
4.2 本地方法栈
和虚拟机栈的作用类似,只不过用来管理本地方法的调用,本地方法不是Java实现的,而是C语言实现的,它是native方法
4.3 程序计数器
记录当前线程执行的字节码的行号,确保多线程执行正常(Java是多线程的,当前线程的CPU资源可能会被其他线程抢夺,有计数器记录就不会出错)
五 *栈帧执行对内存区域的影响
流程:
程序计算器记录字节码执行的行号---局部变量加载到操作数栈中---局部变量在操作数栈中运算---运算结果保存在局部变量表中---。。。---方法执行完---通过完成入口记录的位置---继续执行代码,在这个过程中程序计数器中的行号会随着字节码行号的变动,不断步进。
计数器-操作数栈-局部变量表---完成出口
六、深入理解JVM内存
JVM内存处理流程
默认参数
- jvm申请内存
- 初始化运行时数据区 根据配置参数分配堆、栈、以及方法区的内存大小
- 类加载 class放入方法区,静态和常量也放在方法区
- 执行方法 虚拟机栈的入栈和出栈
- 创建对象 对象创建在堆中,引用在虚拟机栈的局部变量表
总结:
JVM 在操作系统上启动,申请内存,先进行运行时数据区的初始化,然后把类加载到方法区,最后执行方法。
方法的执行和退出过程在内存上的体现上就是虚拟机栈中栈帧的入栈和出栈。
同时在方法的执行过程中创建的对象一般情况下都是放在堆中,最后堆中的对象也是需要进行垃圾回收清理的。
七、内存溢出
分类 | 原因 | 标志 |
堆溢出 | 内存泄漏、内存抖动等 | OutOfMemoryError:Java heap space |
栈溢出 | 死递归,默认1M | StackOverflowError |
方法区溢出 | OutOfMemoryError:Metaspace | |
直接内存溢出 | OutOfMemoryError:Direct buffer memory |
JHSDB工具
可以查看内存(虚拟内存和物理内存都看得到)、查看对象、栈