-
**程序计数器:**由于Java虚拟机的多线程是通过线程轮流切换和分配处理器执行时间的方式来实现,所以在任何时刻一个处理器都会执行一个线程中的指令,因此,为了线程切换后能回到正确的位置,每个线程都要有一个程序计数器,并互不影响。称这类内存区域为“线程私有”(生命周期与线程相同)的内存区域。
-
若线程执行的是一个Java方法,则程序计数器记录的是正在执行的虚拟机字节码指令地址;若执行的是本地方法,值应为空。
-
**虚拟机栈:**每个方法被执行时,Java虚拟机就会创建出一个栈帧,用来存储局部变量表(存放JVM基本数据类型、对象引用和returnAddress类型)、操作数栈、方法出口等信息,方法被调用直到执行完毕的过程,就对应着栈帧从虚拟机栈入栈到出栈的过程。
- 局部变量表所需的内存在编译期间完成分配,在方法运行期间不会改变局部变量表的大小。这里的大小指变量槽的数量,而虚拟机使用多大的空间来实现一个变量槽,是由虚拟机本身决定的。
- 如果线程请求的栈深度大于虚拟机允许的深度,则抛出StackOverflowError异常;如果虚拟机栈容量可以动态扩展,那无法申请到足够空间时会抛出OutOfMemoryError异常
-
**本地方法栈:**虚拟机栈为虚拟机执行Java方法服务,本地方法栈为虚拟机使用到的本地方法服务。会出现的异常同上。
-
**Java堆:**被所有线程共享,在虚拟机启动时创建。目的:存放对象实例。从内存回收角度看,Java堆并没有被分为新生代老年代等区域;从分配内存角度看,Java堆可以分为多个线程私有的分配缓冲区,来提升对象分配时的效率。Java堆存储的只能是对象的实例,细分只是为了更好的回收或分配内存。Java堆可以出于物理上不连续但逻辑上连续的存储空间。可以固定大小也可动态扩展,如果堆内存已满,且无法扩展时,会抛出OutOfMemoryError异常。
-
**方法区:**线程共享,存储已经被虚拟机加载的类型信息、常量、静态变量等。可物理上不连续,可动态扩展。在这个区域垃圾收集行为较少出现。若无法满足新的内存分配需求,会抛出OutOfMemoryError异常。
-
运行时常量池:方法区的一部分。常量池表用来存储编译器生成的各种字面量和符号引用,在类加载后将存放到方法区的运行时常量池中。
-
**直接内存:**频繁使用,也会出现OutOfMemoryError异常。
-
对象的创建:在语言层面上,对象创建就是new一个关键字出来,在虚拟机上,当遇到一个字节码new指令时,首先检查这个指令的参数能否在常量池中定位到一个类的符号引用,并检查这个符号引用是否被加载、初始化过,若没有,则先执行相应的类加载过程。类加载完成后,对象所需内存大小已确定,虚拟机为对象分配内存。【分配方式有指针碰撞和空闲列表,分配方式由Java堆是否规整决定,而Java堆是否规整由垃圾收集器是否有空间压缩整理能力决定(Serial、ParNew带压缩整理->指针碰撞;CMS基于清除算法->空闲列表)。修改指针在并发情况下不是线程安全的,解决:①虚拟机采用CAS配上失败重试的方式来保证更新操作的原子性 ②每个线程在Java堆中预先分配一小块内存,本地线程分配缓冲(TLAB)】内存分配完成后,虚拟机将分配到的内存空间都初始化为零值,从而保证对象实例的字段在Java代码中不赋值就能直接使用。接下来Java虚拟机对对象进行必要设置,例如对象是哪个类的实例、GC分代年龄信息等,这些信息都存放在对象头中。以上完成后,从Java虚拟机角度看,一个新的对象已产生。但对于Java程序,new指令之后会接着执行()方法,按照程序员意愿对对象进行初始化,这样一个对象才算真正构造出来。
-
**对象的内存布局:**对象头、实例数据、对齐填充。
-
**对象的访问定位:**①使用句柄:Java堆中划分出一块内存作为句柄池,reference中存储的是对象的句柄地址,句柄中包含对象实例数据和类型数据的具体地址。②直接指针:reference中存储的直接就是对象地址,如果只要访问对象本身的话,就不需要多一次间接访问开销。使用句柄好处是在对象被移动时只会改变实例数据指针,reference不需要被改变;直接直接好处是速度快。
深入理解Java虚拟机(二)
最新推荐文章于 2024-01-29 21:10:25 发布