JVM详解之JVM的内存区域
一、JVM是什么
1 概述
Jvm是Java Virtual Machine 的缩写,通俗来讲,JVM就是为Java程序提供运行环境的虚拟机
java文件的执行不直接在操作系统上执行,而是通过jvm虚拟机执行,所以其具有跨平台的特性
2 JVM运行流程
3 JVM的组成
3-1 类加载器
类加载器的作用是加载类文件到内存。执行一个.java程序时,通过javac命令进行编译,生成.class文件
字节码文件通过类加载器加载到内存中,通过jvm后续的模块进行加载执行程序
3-2 执行引擎
执行引擎也叫解释器,负责解释命令,提交操作系统执行
3-3 本地接口
其作用是融合不同的编程语言为java所用,一般用于与硬件有关的应用
3-4 运行时数据区
整个JVM的重点。我们所有写的程序都被加载到这里,之后才开始运行。
整个JVM框架由加载器加载文件,然后执行器在内存中处理数据,需要与异构系统交互是可以通过本地接口进行
二 JVM的内存区域
内存区域就是运行时数据区。在JVM的自动内存管理机制下,不需要为每一个对象手动释放内存,不容易出现内存泄露或溢出的问题。
1. 运行时数据区的结构
2 程序计数器
程序计数器是Java虚拟机中一块线程私有的内存区域,
作为当前线程执行字节码的行号指示器,通过改变其值来选取下一条要执行的字节码指令,支持线程恢复功能。
在JVM多线程切换时,每个线程需要有独立的程序计数器以记录各自的执行位置,互不影响。
执行Java方法时,它记录字节码指令地址,执行Native方法时,程序去计数器为空。
因为此时Java虚拟机调用的是和操作系统相关的接口,接口的实现不是Java语言,而是C语言和C++
它的生命周期随着线程的创建而创建,随着线程的结束而结束
3 Java虚拟机栈
Java虚拟机栈是每个线程私有的"内存草稿本“,专门记录Java方法是如何运行的。每个方法就像一张草稿纸(栈帧),方法开始时拿一张写,结束时扔掉。
Java虚拟机栈专门管理Java代码的执行
4 本地方法栈
专门管理外部Native(非Java)代码的执行,内部通过栈帧管理。
在HotSpot虚拟机只有“Java虚拟机栈”这一个栈,但它在处理Java方法和Native方法时会使用不同的逻辑,但内存管理机制是统一的。
5 Java堆
Java堆是JVM中最大的一块共享内存(启动时创建),专门存放对象实例和数组,也是垃圾收集器管理的主要区域。因采用分代回收算法,分为新生代和老年代,目的是优化内存回收和分配。
6 方法区
方法区是共享的内存区域,用于存储类信息,常量,静态变量,即使编译器优化后的代码等,是堆的逻辑部分
特性:内存要求宽松,垃圾回收可选且困难。
双引号字符串存于常量池中,new字符串在堆中,inter()方法返回常量池中字符串的引用
6-1 运行时常量池
运行时常量池是方法区的一部分。Class文件中除了有类的版本,字段,方法,接口等描述信息外,还有常量池信息(用于存储编译期生成的各种字面量和符号引用)。
7 直接内存。
直接内存不属于JVM运行时数据区的一部分,但是这部分内存区域被频繁的调用,也可能发生OutMemoryError异常。
三、Java对象的创建过程
Java对象创建流程:
1. 类加载过程
虚拟机遇到一条new指令时,先检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载,解析和初始化过。如果没有,那必须先执行相应的类加载过程。
2. 分配内存
类加载检查通过后,虚拟机需要在Java堆中为对象分配内存。分配方式取决于堆内存是否规整,而规整性由垃圾收集器决定。
2-1 指针碰撞:
堆内存规整时,用指针分隔已用与空闲内存,分配时只需移动指针即可
2-2 空闲列表
堆内存不规整时,虚拟机维护可用内存块列表,分配时查找并更新列表
2-3 并发分配内存需解决线程安全问题
CAS+失败重试:通过比较和交换机制确保操作原子性,若旧值未变,则更新为新值,否则重试
TLAB(本地线程分配缓冲):为每个线程分配私有内存,线程在专属空间内存分配内存,减少同步开销
3. 初始化零值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值,这一步操作保证了对象的实例字段在java代码中可以不赋予初始值就直接使用。
4. 设置对象头
初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等信息。
这些信息存放在对象头中。
5. 执行init方法
上面工作都完成后,所有的字段都还为零。执行new指令后会接着执行Init方法,把对象按照程序员意愿进行初始化。
四、 对象的访问定位
建立对象就是为了使用对象,我们的Java程序通过栈上的reference数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有使用句柄和直接指针两种
1. 使用句柄
如果使用句柄,那么Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息
2 直接指针
如果使用直接指针访问,那么Java堆对象的布局中就必须考虑如何防止访问类型数据的相关信息,而reference中存储的直接就是对象的地址
2-1 使用句柄访问的好处:
reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。
2-2 使用直接指针访问的好处:
速度更快,节省了一次指针党委的时间开销。