JVM 01 运行区域
Java 虚拟机 跨平台
虚拟机隐藏平台差异,解决不同平台代码运行结果不一致问题,实现Write Once, Run Anywhere
,实现用户代码跨平台。它本身是一个操作系统上的应用程序,将字节码文件翻译成特定机器的机器码。
Java 虚拟机 运行时内存区域
虚拟机隐藏内存区域,自动实现内存分配与垃圾回收,使用户专注逻辑实现。
JVM 将运行时数据区分成五块:堆,方法区,虚拟机栈,本地方法栈,程序计数器。
堆是内存区域最大的一块,存放对象实例。所有线程共享。它是垃圾回收器 GC 主要管理的区域。堆中也存在线程私有区域(Thread Local ALlocation Buffer)以提升效率。Java 参数 -Xmx -Xms 可以设定 Java 堆大小。
方法区也是线程共享区域。相对于堆,垃圾回收没有那么频繁,条件也更苛刻。它存放存储虚拟机加载的类型信息,常量,静态变量,即时编译器代码缓存。垃圾回收主要针对常量池和类型卸载。JVM 将字节码文件中的字面量,符号引用放入运行时常量池。内存不足也报OOM。
虚拟机栈对应线程。每个线程一个栈,且二者生命周期相同。线程执行的方法对应栈帧。栈帧保存局部变量表,操作数栈,返回地址等。调用和返回方法对于栈帧入栈和出栈。如果无限递归,栈深度超出阈值,报StackOverflowError。如果内存不足,OOM。
局部变量表存放方法所需的局部变量和返回地址。对于基本类型,直接存放值,对于引用类型,存放指针。局部变量表基本单位是四字节的 slot。小于四字节的 boolean,byte,char,short 变量也占据一个 slot。
本地方法栈与虚拟机栈类似,执行的是 native 方法,即非 Java 方法。
程序计数器指示当前线程执行字节码行号,用于控制程序流程。线程上下文切换期间记录当前线程执行行号,下次获取时间片后从当前行号继续执行。程序计数器不会内存溢出,线程私有。
直接内存不是虚拟机运行数据区一部分,它不由 JVM 管理,不受 JVM 大小限制。NIO 使用本地方法分配堆外内存(即直接内存,机器直接分配的内存),通过堆内的 DirectByteBuffer 对象引用堆外内存,避免数据在堆内堆外来回复制。
对象的创建
第一步,要求虚拟机已经加载类。
第二步,为对象分配内存。类加载时就确定大小。分配内存的方法有:指针碰撞和空闲列表。指针碰撞:将堆分为两块,一块已分配,另一块未分配,指针为边界,两块不能有交错。移动指针分配空空闲内存。空闲列表:已分配与未分配交错,维护一个列表,记录可用列表块。虚拟机采用CAS+重试实现线程安全地分配内存。
第三步,初始化内存,将实例字段初始化为零值。
第四步,设置对象头。
第五步,执行构造函数。
对象布局
对象在堆内存中分三块:对象头,实例数据,对齐填充。对象头包含两部分:对象运行时数据和类型指针。运行时数据包括:GC 年龄,哈希码,锁状态。类型指针指向类型元数据。如果是对象是数组,还包含数组长度。
实例数据包含对象字段。父类定义的变量在子类之前,相同长度的字段总是分配在一起。
对齐填充:对象起始地址必须是八字节的整数倍。