java虚拟机内存分布与对象揭秘

导读

        与c++程序员不同,java程序员在编写代码的过程中并不需要直接与内存打交道,而是把内存分配与动态回收的任务交给java虚拟机。这大大简化了程序员编写代码的工作,但同时由于java程序员不需要关心内存的使用,导致其对虚拟机自动管理内存的机制了解不足,每当程序出现内存相关的异常或错误时,程序员往往无从下手。所以作为一名合格的java程序员,还是有必要对java虚拟机的内存管理做一些基本的了解。

 

数据区域划分

        java虚拟机在执行代码的过程中,将管理的内存分为若干个区域,每个区域对应着特定的作用。其中部分区域是线程私有的,随着线程的发起而产生,随着线程的撤销而消亡,其他区域则是由所有线程共享,例如存储对象的堆。内存区域的大致划分如下图所示:

线程隔离的数据区

线程隔离数据区指的是依附于一个线程的数据区,包括虚拟机栈、本地方法栈和程序计数器。

1、程序计数器(Program Count Register):程序计数器是用来记录当前线程执行的位置,是线程执行字节码的行号指示器。每个线程拥有一个程序计数器,各程序计数器之间互不影响。由于程序计数器所占空间较小,一般情况下该区域不会出现OutOfMemoryError的情况。

2、Java虚拟机栈(VM Stack):虚拟机栈描述的是java方法执行的内存模型,每执行一个方法会创建一个栈帧(stack frame),用于存储局部变量变量表、操作数栈、动态链接、方法接口等信息。如果线程请求的栈深度超过虚拟机所允许的最大栈深度,会抛出StackOverFlowError异常;如果虚拟机在动态扩展的过程中无法请求到足够的内存,则会抛出OutOfMemoryError异常。

3、本地方法栈(Native Method Stack):本地方法栈的功能与java虚拟机栈的功能类似,只不过java虚拟机栈是虚拟机执行java方法,而本地方法栈是java虚拟机执行native方法。同java虚拟机类似,该区域也会抛出StackOverFlowError异常和OutOfMemoryError异常。

线程共享数据区

线程共享数据区是java虚拟机上所有线程共享的内存区域,主要包括Java堆和方法区。

1、Java堆(Java Heap):对于绝大多数应用来说,Java堆是虚拟机管理内存的最紧要的部分。它主要用来存储对象实例。Java堆是垃圾收集管理的主要部分,因此又称为GC堆(Garbage Collection Heap)。从内存回收的角度来说,java堆可以分为新生代和老年代,进一步划分又可以分为Eden空间、From Survivor空间、To Survivor空间。从内存分配的角度来说,java堆又可以分为多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。当java堆中没有足够的空间分配实例对象且无法扩展时,该区域也会抛出OutOfMemory异常。

2、方法区(Method Area):该区域用于存储已加载的类信息、静态变量、常量、JIT编译后的代码等数据。对于HotSpot虚拟机,开发人员又将方法区习惯称为“永久代”(Permanent Generation)。当方法区无法分配足够多的内存时,会抛出OutOfMemoryError异常。运行时常量池(Running Constant Pool)是方法区的一部分,它用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载进入方法区后进行存放。

直接内存(Direct Memory)

直接内存并不是虚拟机运行时数据区的一部分,但在动态扩展的过程中也会抛出OutOfMemmoryError异常。在JDK1.4后加入了NIO类(New Input/Output),引入了一种基于通道和缓冲区的IO方式,它可以使用Native方法直接分配堆外内存,然后通过一个存储在堆上的DirectByteBuffer对象作为这块内存的引用对进行操作,避免了在java堆和Native堆间来回复制数据,提高了性能。

 

HotSpot对象揭秘

对象创建

虚拟机在遇到一条new指令时,首先根据参数在常量池中寻找一个类的符号引用,找到了,证明该类已加载到方法区,否则类未被加载,则先加载该类到方法区。在检查完类的加载之后,虚拟机要为新生的对象分配内存,内存大小可由类加载信息得知。内存分配完成后,虚拟机将分配到的内存空间初始化为0值,再对对象的头部信息进行初始化,最后对对象的实例信息进行初始化。

对象的内存布局

在HotSpot虚拟机中,对象的内存分布主要可分为3个部分,即对象头、实例数据和对齐填充。对象头又包括两部分信息,第一部分用于记录对象运行时的数据,如哈希码(HashCode)、GC分代年龄以及锁信息,第二部分则是类型指针,它指向该对象所对应的类元数据,虚拟机通过这个指针确定这个对象属于哪个类。实例数据对应的是对象真正存储的有效信息。对齐填充主要用于实例数据未能对齐时的填充工作。

对象的访问定位

建立对象是为了使用对象,java程序在栈中使用对象的reference来操作对象。而reference定位到对象的方式又分为两种,一种是reference直接指向对象在java堆上的内存头地址,另一种reference是指向对象对应的句柄。对象在创建的过程中同时在堆上创建一个句柄,句柄指向对象的头地址。这两种定位方式各有优点,直接定位的优点是速度快,节省了一次指针定位的时间开销,使用句柄的优点是在对象移动时,只需修改句柄值,而reference值并不用做改变。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值