关于学习java虚拟机的知识整理一:jvm内存区域

之前由于考研,对于虚拟机的认识疏忽了太多,现在重新整理回顾一下。

如上图所示,jvm的内存区域(运行时数据区)共分为5处:方法区(Method Area)、虚拟机栈(vm Stack)、本地方法栈(Native Method Stack)、堆(Heap)和程序计数器(Program Counter Register)

程序计数器:它是一块内存较小的区域,可以看成是线程所执行的字节码的行号指示器,字节码解释器运行时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。在java虚拟机中,多线程的运行采用的的是线程轮流切换机制,所以每条线程都需要建立一个独立的程序计数器,它是线程私有的。

虚拟机栈:它和程序计数器一样,也是线程私有的,它的作用是当java线程运行时,java方法在执行的同时会建立一个栈帧,用来存储局部变量表、操作数栈、动态链接,方法出口等信息。每个java方法从调用到执行完成的过程,就对应着一个栈帧从入栈到出栈的过程。局部变量表所需的内存空间在编译期间分配完成,所以当进入一个方法时,需要在帧中分配多大的局部变量空间是完全确定的(除了long和double类型的数据会占用2个局部变量空间外,其他基本数据类型仅占用1个局部变量空间),在方法运行期间并不会改变局部变量表的大小。如果线程请求的栈深度大于虚拟机的规定,将抛出StackOverFlowError异常;如果虚拟机栈动态扩展时无法申请到足够的内存空间,将会抛出OutOfMemoryError异常。

本地方法栈:它与虚拟机栈所发挥的作用相似,区别就是虚拟机栈为java方法服务,而本地方法栈为Native()方法服务,抛出异常痛上。

java堆:堆(Heap)是java虚拟机所管理内存的内存中最大的一块。java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,存放对象实例。当Heap没有内存可以完成实例分配,而且堆也无法再扩展时,将会抛出OutOfMemoryError异常

方法区:与java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量和即时编译器编译后的代码等数据。虽然java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它有一个别名Non-Heap(非堆),提醒读者将它与java区分开。java虚拟机堆方法区的限制非常宽松,除了和java堆一样不需要连续的内存空间和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。当方法区无法满足内存分配需求时,将会抛出OutOfMemoryError异常。

此外,还有一个特殊区域——运行时常量池(Runtime Const Pool)是方法区的一部分。除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。另外一个重要特征是具有动态性,java语言并不要求常量一定只有编译器才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用的比较多的便是String类中Intern()方法。

对象的创建

当虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载,如果没有,那必须先执行相应的类加载过程,类加载检查通过后,接下来就虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从java队中划分出来,内存分配完成以后,虚拟机需要将分配到的内存空间都初始化0值(不包括对象头)(这一步操作保证了对象的实例字段在java代码中可以不赋初始值就直接使用),接下来,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例,在上面的工作都完成以后,从虚拟机的视角来看,一个新的对象已经产生了,但从java程序的视角来看,对象创建才刚刚开始——<init>方法还没有执行,所有的字段都为0.

对象的内存布局

在HotSpot虚拟机中,对象在内存中的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

对象头:包括两部分信息,一是用于存储对象自身的运行时数据,如哈希码、GC分代年龄等,官方称为“Mark Word”。二是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,如果对象是一个数组,那对象头中还必须有一块记录数组长度的数据。

实例数据:对象真正有效的信息,也是在程序代码中所定义的各种类型的字段内容。无论是来自父类还是子类,都需要记起来。这部分的存储顺序会受到虚拟机分配策略参数(FieldsAllocationStyle)和字段在Java源码中定义顺序的影响。

对齐填充:占位符,并非必然存在,没有特别的含义。

对象的访问定位

java程序需要通过栈上的reference数据来操作堆上的具体对象。java虚拟机规范中只规定reference类型只是一个指向对象的引用,并没有规定这个引用应该通过何种方式定位、访问堆中的对象的具体位置,所以对象的访问也是取决于虚拟机实现而定的。目前主流的访问方式时使用句柄和直接指针两种。

如果使用句柄访问的话,java堆中就会划分出一块内存来作为句柄池,reference类型中存储的就是对象的句柄类型,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。优点是句柄地址稳定,对象被移动后,只需改变句柄中的实例数据指针,而reference本身不需要修改。

如果使用直接指针访问,那么java堆对象的布局就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。优点是速度更快。

猜你喜欢

转载自www.cnblogs.com/ai-shang/p/12377771.html
今日推荐