1.JVM组成
- 类加载器 ClassLoader
- 运行时数据区 Java Runtime Data Area
- 执行引擎 Execution Engine
- 本地库接口 Native Interface
java程序在执行之前通过编译将java代码转换为字节码class文件。jvm首先需要把字节码文件通过类加载器加载到内存中的运行数据区。
字节码文件时jvm的一套指令集规范,操作系统无法识别,不能交友操作系统执行,因此需要特定的命令解析器。执行引擎将字节码文件翻译成对应操作系统的指令在交由CPU执行。
在以上过程中需要嗲用其他语言的接口本地库接口来实现整个程序的功能。
2.运行时数据区组成
①程序计数器 Program Counter Register 线程私有
一块较小的内存控件,记录当前线程代码执行的行号。当CPU时间片被其他线程抢走,本线程挂起后,程序计数器记录程序执行的位置,当本线程重新运行时便会从挂起位置开始运行。当执行native方法时,计数器为空。
②java虚拟机栈 VM Stack 线程私有
又名线程栈,即每个线程都会有一块独立的栈内存空间,在栈中方法以FILO的方式存入栈中(与java代码执行顺序相符),每一个方法在栈中都对应一块栈帧用于存放局部变量表,操作数栈,动态链表和方法出口等信息。每个方法从调用到执行完的过程就对应着一个栈帧在VM Stack由入栈到出栈的过程。
局部变量表:存放方法中的局部变量,若该变量为对象,则存放对象的引用reference。
操作数栈:在程序运行过程中,局部变量表的变量做运算的一块临时内存空间。
动态链接:通过动态链接可以找到方法中的代码在方法区的地址。
方法出口顾名思义。
③本地方法栈 Native Method Stack 线程私有
与java虚拟机栈相似,本地方法栈只为Native方法服务。
④java堆 Java Heap 线程共享
jvm管理的内存最大的一块,唯一作用存放对象实例。是垃圾收集器管理的主要区域。
⑤方法区 Method Area 线程共享
存储class类元信息,静态变量,常量,静态方法等。同栈类似,若静态变量是对象则存放对象的引用reference。
方法区在JDK1.8之后是直接内存了。
3.运行时数据区各部分的分工
如图所示:
- 当一个Math.class被类加载器ClassLoader装载进运行时数据区后,Math.class,Math中的静态变量、常量、静态方法都会存放在方法区。若静态变量为对象,则在方法区存对象的reference,对象实例存在堆中。
- 当执行Math中的main方法时便开启一个线程栈,并创建main方法的栈帧入栈,然后main方法调用了math方法,则入栈一个math栈帧。与此同时由执行引擎在程序计数器中记录代码运行的行数。
- 在栈帧中若变量为对象,则局部变量表中存放对象的reference,对象实例存在堆上,而普通变量则直接存放在局部变量表中,随着math的执行完毕出栈而销毁。
4.堆内存结构
- 堆内存时java运行时数据区最大的一块内存空间,同时也是对象实例存储地。java堆可以处于物理上不连续的内存空间,逻辑上连续即可。
- java堆中分为Eden区,Survivor区,和老年代,其中前二成为新生代。Survivor区又分为S1和S2。
- Eden区存放刚被创建的新对象,也就是未曾被GC扫描过的对象。当Eden区被占满时,执行引擎便会执行minor GC将未被回收的对象转移至survivor区,对象分代年龄加1。此后每次GC分代年龄都会加1,且S区的对象会在S1和S2之间来回切换。当分代年龄达到15时,移至老年代。
- 当老年代中内存占满开始执行full GC。full GC时对整个堆进行GC,资源耗费极大。
5.对象内部结构
一个对象由对象头、实例数据、对齐填充组成;
实例数据就是对象中真正存放的实际数据;而对齐填充是满足虚拟机规定的对象大小的占位符,如对象大小必须是8字节的整数倍。
对象头:
- 存有对象的哈希吗,GC分代年龄,锁状态标记,线程持有的锁,偏向线程ID,偏向时间戳等。
- 类型指针;对象指向它的类元数据的指针,虚拟机通过类型指针来确定这个对象是哪个类的实例。若对象是一个数组,则还会在此有一块记录长度的数据。
6.创建一个对象的过程
- 启动jvm虚拟机,将磁盘中的class文件加载 道jvm运行时数据区的方法区。
- 当jvm遇上new指令后,在堆上为这个对象划分一块内存空间。
- 将分配的内存空间初始化为零,并调用<init>()方法初始化对象
- 设置变量指向刚分配的内存地址。