一、JVM运行时的数据区域
Java虚拟机在Java程序执行的过程中会把它所管理的内存分成若干个不同的数据区域。
包括:
1.程序计数器(Program Counter Register)
我们知道,Java是一次编译,到处运行的一种语言。
这种跨平台特性是基于Java的.class文件字节码实现的。
第一次编译: Java源程序 => .class文件字节码(可以到处运行的字节码)
第二次编译: .class文件字节码 => 机器码 (只要装有JVM,就可以将字节码转换成机器码)
简单来说,程序计数器是当前线程执行的字节码的 行号指示器。
在Java虚拟机的概念模型里,字节码解释器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
每个Java线程独立拥有一个程序计数器。
因此,这类内存区域是 “线程私有”或者说“线程隔离”的。
2.Java虚拟机栈
虚拟机栈描述的是Java方法执行的内存模型:
每个方法在执行时都会创建一个帧栈(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
每一个方法调用到执行完成的过程,对应一个帧栈在虚拟机栈中入栈到出栈的过程。
3.本地方法栈
该内存区域是线程私有的
与虚拟机栈类似,虚拟机栈是为虚拟机执行Java方法(字节码)服务的,本地方法栈是为虚拟机使用到的Native方法服务。
4.Java堆
Java堆主要放的是对象实例。
Java堆是线程共享的,是垃圾收集器管理的主要区域。
很多时候Java堆也叫“GC堆”。
Java堆可以处于物理上不连续的内存空间中。
5.方法区
用于存储已经被虚拟机加载的
类信息、常量、静态变量、编译后的代码等数据。
这个内存区域也是线程共享的。
运行时常量池是方法区的一部分,Java编译器生成的各种字面量和符号引用在类加后进入方法区的运行时常量池存放。
如何理解字面量和符号引用?
字面量就是比如说int a = 1; 这个1就是字面量。又比如String a = "abc",这个abc就是字面量。
在java中,一个java类将会编译成一个class文件。在编译时,java类并不知道引用类的实际内存地址,因此只能使用符号引用来代替。比如org.simple.People类要引用org.simple.Tool类,在编译时People类并不知道Tool类的实际内存地址,因此只能使用符号org.simple.Tool(假设)来表示Tool类的地址。而在类装载器装载People类时,此时可以通过虚拟机获取Tool类 的实际内存地址,因此便可以既将符号org.simple.Tool替换为Tool类的实际内存地址,及直接引用地址。
二、Java对象与JVM的关系
2.1Java对象的创建过程
1.检查new指令的参数是否能在常量池中定位到一个类的符号引用。
2.检查类是否加载、解析和初始化过,若没有,先执行类加载过程。
3.为对象分配内存。
Java的内存分配有两种机制,取决于Java堆内存是否规整,而堆内存是否规整又取决于GC是否带有压缩整理功能决定。
指针碰撞,已用内存和未用内存的分界线处有一个指针,把指针往空闲区域挪一挪就叫指针碰撞。
空闲列表,虚拟机维护一个列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间。
如何考虑修改指针位置的并发情况?CAS+失败重试 或 TLAB
4.虚拟机将分配到的内存空间初始化为0值。
5.对对象进行必要的设置(哪个类的实例、找到类的元数据信息、对象哈希码、对象GC分代年龄等)。
虚拟机的视角来看,已经完成了对象的创建。从Java程序来看,init还没有执行。
2.2 对象的内存布局
对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)
对象头(32bit和64bit,看虚拟机是多少bit)
1.存储对象自身的运行时数据(HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID等)
2.类型指针(即对象指向它的元数据的指针)。理解为,JVM通过这个指针找到该对象属于哪个类。
实例数据
1.对象真正存储的有效信息,就是代码中定义的各种类型的字段。
2.这些字段的存储顺序具有一定的规律,受到JVM分配策略参数(FieldsAllocationStyle)的支配。
对其填充
这一部分不是必然存在的,有的VM要求对象起始地址必须是8字节的整数倍,换句话说,对象大小必须是8字节的整数倍。
2.3 对象的访问定位
Java程序通过栈上的reference数据来操作堆上的具体对象,reference类型只是一个指向对象的引用,JVM规范没有定义如何通过这个引用去定位堆中对象的具体位置。
主流有两种定位方式: