1.运行时数据区
程序计数器:线程私有的内存。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转都需要依赖这个计数器来完成。 如果一个线程正在执行java方法,则计数器记录的正在执行的虚拟机字节码指令的地址;如果正在执行的是native方法,则计数器的值为空。 唯一的无OOM情况的区域。java虚拟机栈:线程私有的。每个方法在执行的同时都会创建一个栈帧用于存储 局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用到执行完成的过程就对应着栈帧在虚拟机栈中入栈出栈的过程。 局部变量表存放了编译期间可知的各种基本数据类型(boolean,byte,char。。。。其中64位的long,double占用2个Slot,其余占用一个)、对象引用类型、returnAddress类型(指向了一条字节码指令的地址),局部变量表在编译期间完成内存分配。
本地方法栈:与虚拟机栈类似,只是他执行Native方法。
Java堆:线程共享区域,存放对象实例
方法区:线程共享区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。也称为“永久代”----代表垃圾回收在该区域比较少出现。 运行时常量池是方法区的一部分。Class文件中除了有类的版本,字段、方法、接口等描述信息外,还有一个就是常量池,用于存放编译期间生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池。
对象的创建
在语言层面上创建对象仅仅是new关键字而已,而在虚拟机中对象的创建1.虚拟机遇到new指令,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否被加载、解析、初始化。若无,则执行类加载。 2.加载检查通过后,虚拟机为新生对象分配内存。对象所需的内存在类加载完成后便可知 3.内存分配完成后,虚拟机需要将分配到的内存空间都初始化为0值(不包括对象头) 4.接下来虚拟机要对对象进行必要的设置,例如对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码等,这些信息存放在对象头中。
上面工作都完成后,从虚拟机的视角看,一个新的对象已经产生了,但从java程序的视角看对象的创建才刚刚开始------<init>方法还没执行,所有字段都还为0,所以一般来说(由字节码中是否跟随invokespecial指令决定)中执行new指令之后,还会接着执行init方法,把对象按程序员的意愿进行初始化,这样一个有用的对象才算产生出来。
对象的内存布局
分为3个区域:对象头,实例数据,对齐填充。、 对象头:包括2部分信息,第一部分用于存储对象自身运行时数据,如哈希码、GC分代年龄、锁状态标志。 另一部分是类型指针,即对象指向它的类元数据指针,虚拟机通过这个来确定这个对象是哪个类的实例。 实例数据:存储对象真正的有效信息,无论是父类继承下来的还是在子类中定义的。 对齐填充: 占位符,因为HotSpot VM的自动内存管理系统要求对象起始地址必须要是8字节的整数倍。对象的访问定位
建立对象是为了访问对象,java程序通过栈上的reference数据操作堆上的具体对象。 主流访问方式有使用句柄和直接指针两种方式。句柄:由于reference中存储的是稳定的句柄地址,在对象被移动时(如GC过程中的对象移动),只需改变句柄中实例数据指针,而reference本身不用动。 直接指针:速度快,节省了一次指针定位的时间开销。HotSpot采用此方式
-------------参考资料《深入理解java虚拟机》