java虚拟机基础知识(一):运行时数据区分析与虚拟机对象分配,布局,访问过程

一、运行时数据区域

线程共享数据区:方法区,堆

线程隔离数据区:虚拟机栈,本地方法栈,程序计数器

 

1、程序计数器【线程私有】:可看作是当前线程所执行的字节码的行号指示器。

----当线程执行的是java方法,计数器记录的是正在执行的虚拟机字节码指令的地址

----当线程执行的是Native方法,计数器值则为空

此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域

2、java虚拟机栈【线程私有】:生命周期与线程相同,描述的是java方法执行的内存模型:每个方法在执行时都会创建一个栈帧用于存储局部变量表,操作数栈,动态连接,方法出口等信息。每一个方法从调用到执行完成的过程,对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

   局部变量表存放了基本数据类型(boolean,byte,char,short,int,long,float,double),对象引用(reference类型)和returnAddress类型;其中64位长度的long和double类型占用2个局部变量空间(slot),其余的占一个。

----如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常

----虚拟机栈可动态扩展,若扩展时申请不到足够内存,将抛出OutOfMemoryErrory异常

3、本地方法栈【线程私有】:类似于虚拟机栈,只不过虚拟机栈为虚拟机执行java方法(也就是字节码)服务,而本地方法栈为Native方法服务。同样本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常

4、Java 堆【线程共享】:管理内存中最大的一块区域。是垃圾收集器管理的主要区域,也叫“GC堆”。划分方式与存放内容无关,存储的都是对象实例

----从内存回收角度:采用分代收集算法,可分为新生代和老年代;或者分为Eden空间,From Survivor 空间,To Survivor空间等

----从内存分配角度:划分多个线程私有的分配缓冲区

----如果在堆中内存没有完成实例分配,且堆也无法扩展,将抛出OutOfMemoryError异常

5、方法区【线程共享】:用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。,可通过-XX:PermSize和-XX:MaxPermSize限制方法区大小

----如果方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

6、运行时常量池:是方法区的一部分。除了保存Class文件中描述的符号引用,也存储翻译出来的直接引用。相比于Class文件常量池,运行时常量池具有动态性,并不要求常量一定只有编译器才能产生,典型代表:String类的intern()方法

----当常量池无法再申请到内存时将会抛出OutOfMemoryError异常

7、直接内存:并非虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。容易在配置虚拟机参数时忽略

二、HotSpot虚拟机对象分配,布局,访问过程

1、对象的创建过程:虚拟机遇到一条new指令时,

首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载,解析和初始化。如果没有,那必须的先执行相应的类加载过程。

类加载检查通过后,接下来虚拟机将为新生对象分配内存

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)

接着虚拟机要对对象进行必要的设置(如该对象是哪个类的实例,对象的哈希吗等信息)

执行new指令之后会接着执行<init>方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来

----对象内存分配方式:

  • 指针碰撞:当java堆中内存绝对规整,用过的内存放一边,空闲的放一边,中间放一个指针作为分界点的指示器,让指针向空闲空间那边挪动一段与对象大小相等的距离。【使用者:Serial,ParNew等待Compat过程的收集器】
  • 空闲列表:当java堆中的内存不规整,已使用的内存和空闲内存相互交错,虚拟机通过维护一张表,记录可用内存块,在分配内存的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录【使用者:CMS这种基于Mark-Sweep算法的收集器】

----解决并发情况下频繁创建对象线程不安全的问题:

  • 方式一:对分配内存空间的动作进行同步处理(实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性)
  • 方式二:本地线程分配缓冲(TLAB):把内存分配的动作按照线程划分在不同的空间之中进行,只有TLAB用完并分配新的TLAB时,才需要同步锁定,可通过-XX:+/-UserTLAB参数设定

2)对象的内存布局:对象头(Header),实例数据(Instance Data)和对象填充(Padding)

----对象头包括两部分信息:第一部分用于存储对象自身的运行时数据;第二部分是类型指针

----实例数据:是对象真正存储的有效信息。HotSpot虚拟机默认的分配策略为longs/doubles,ints,shorts/chars,bytes/Booleans,oops(Ordinary Object Pointers)

----对齐填充,非必然存在,起占位符作用,对齐填充。

3)对象的访问定位:

方式一:使用句柄,这种方式下java堆中会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址。好处:在对象被移动(垃圾收集)时只会改变句柄中的实例数据指针,而reference不需要修改

方式二:使用直接指针访问,这种方式下java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。好处:速度更快,节省了一次指针定位的时间开销

 

猜你喜欢

转载自blog.csdn.net/qq_38966361/article/details/88294997
今日推荐