程序计数器是一块较小的内存空间.它可以看作是当前线程所执行的字节码的行号指示器. 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成.
为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器.如果线程正在执行的是一个Java方法,那么计数器记录的是正在执行的虚拟机字节码指令的地址.
与程序计数器一样,Java虚拟机栈也是线程私有的,他的生命周期与线程相同.每个Java方法在执行时都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息.每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程.
栈中的局部变量表存放了编译期可知的各种基本数据类型(int, long, double,float,short,byte,char,boolean)、对象引用(类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是一个指向一个代表对象的句柄或其他与此对象相关的位置)、returnAddress类型(指向了一条字节码指令的地址)。
本地方法栈为虚拟机使用到的native方法服务。
Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。次区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。也可以叫做GC堆。
方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的`类信息、常量、静态变量、即时编译器编译后的代码等数据。
是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
不是虚拟机运行时数据区的一部分,就是本地的内存。但也会使用到。比如NIO。
虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。在类加载检查通过后,虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。
虚拟机采用CAS配上失败重试的方式保证更新操作的原子性。内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)。
接下来虚拟机要对对象进行必要的设置,比如这个对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等信息。都存储在对象头中。
到这里,一个新的对象才算创建完成,后面还要根据代码进行初始化。
对象在内存中存储的布局可以分为3个区域。对象头,实例数据和对齐填充。
对象头包含两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。。另一部分是类型指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果对象是Java数组,那么对象头还必须有一块用于记录数组长度的数据。
实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类集成的,还是在子类中定义的,都需要记录下来。
第三部分对齐填充并不是必然存在的,仅仅起着占位符的作用。
原文:https://www.cnblogs.com/shiguangqingqingchui/p/12118450.html