深入理解java虚拟机-java内存区域

本文深入解析Java虚拟机(JVM)的内存管理机制,包括方法区、虚拟机栈、本地方法栈、堆和程序计数器等功能区域的作用及区别。特别介绍了对象创建过程中的内存分配策略,并探讨了内存溢出的各种情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java把内存的管理交给了虚拟机,由虚拟机来自动管理内存。所以需要了解java虚拟机内存的管理机制,才能更好的编写程序。

内存区域

java虚拟机在执行程序时会把它所管理的内存分为以下区域:

  1. 方法区
  2. 虚拟机栈
  3. 本地方法栈
  4. 程序计数器

    程序计数器:
    程序计数器是一个很小的内存空间,我们知道程序是有一大堆指令和数据构成的,程序计数器则是来指向当前正在执行指令的地址;但是有一点要注意,如果执行的是navtive方法,那么这个计数器则为空。而且每个线程都有一个独立的程序计数器。
    Java虚拟机栈和本地方法栈:
    我们知道栈的特性是先进后出,我们调用一个方法,然后会创建一个栈帧,栈帧会压入栈中,栈帧来用于存储局部变量表,操作数栈,动态链接,方法出口等信息。java虚拟机栈和本地方法栈的区别在于虚拟机栈执行的是java方法,而本地方法栈则是执行的native方法。在栈区有一定的深度,如果无法申请到足够内存,那么就会栈溢出或者内存溢出。
    Java堆:
    栈是线程私有的区域,而堆则是线程共享的区域。所有的对象基本都是在堆上分配内存。java堆是垃圾收集器主要管理的区域,我们知道c语言中的堆,我们可以用malloc来申请内存空间,我们用free来释放空间,而java虚拟机中的堆区是由虚拟机来自动管理的,用垃圾回收器来进行回收。
    方法区:
    方法区也是线程共享的内存区域,它被用于存储被虚拟机加载的类,常量,静态变量,即时编译器编译后的代码和数据。HotSpot把GC分代拓展到了方法区,把方法区归为了永久代,这样不会对这块内存空间进行回收。但是到了jdk1.7,htospot将原有放在永久代中的字符串常量池移了出来,gc也可以对常量池进行回收,所以不能说方法区内的东西不会被回收。
    直接内存:
    直接内存不是虚拟机运行时数据区的一部分,是由native函数库直接分配的堆外内存,通过存储在java对中的directbytebuffer来引用这块内存。
    对象创建:
    java的对象创建,将对象分配在java堆区。分配的方式有两种,一种是指针碰撞,一种是空闲列表;如果java堆的内存是规整的,那么通过移动指针的方式来分配内存,也就是指针碰撞;如果不是规整的,那么通过记录一张空间列表的方式来分配内存,就是将内存分配的区域记录到一张表上。在指针碰撞分配内存空间的过程中,多线程环境下 会有移动指针并发的问题,两种办法解决这个问题:一是采用CAS配上重试来保证操作原子性,第二种是给每个线程预先分配一块本地线程分配缓冲。内存分配完之后,虚拟机需要将分配的内存空间初始化为零,接着对对象进行设置,将对象的信息保存在对象头中。但对于程序来说,只有放init方法执行完后,才产生了一个真正的对象。对象在内存中分为对象头,实例数据和内存的对齐填充。

对象的访问定位

我们知道在c语言中,malloc申请的空间是分配在堆区,返回的指针我们可以作为一个局部变量存在栈区。
在java中,有两种访问方式,一种是使用句柄,另一种是直接指针。
句柄访问:
句柄访问的方式是将堆划分为句柄池和实例池,将java实例对象放在实例池中,划分出一块内存来作为句柄池,句柄包含了指向了对象实例的指针(实例池,堆)和指向对象类型数据的指针(方法区)。在栈中保留一个reference引用类型,指向这个句柄的地址。这种方式的好处就是:当java对象移动时不需要改变栈reference的值(在进行垃圾回收时对象的移动非常频繁),只需要改变句柄中指向对象实例的指针值。
这里写图片描述
直接指针:
直接指针的方式是直接将对象存在java堆中,而这个实例对象还需要留一块区域来保存指向对象类型数据的指针,同时这个实例对象保存实例数据。栈中保留一个reference类型指向这个实例对象。这种方式的好处就是访问java对象的时候比较快。
这里写图片描述
在hotspot虚拟机中是采用第二种方式来进行对象访问,也就是说直接指针访问方式。

内存溢出

堆内存溢出:
当对象实例化超出堆的大小时,就会产生内存溢出异常。
栈溢出:
首先在hotspot中不区分虚拟机栈和本地方法栈,在虚拟机规范中定义了两种异常:
如果线程请求的栈深度超出虚拟机允许范围,则抛出StackOverFlowError异常;
如果虚拟机在拓展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。
然而在单线程的环境下往往抛出的是StackOverFlowError异常.在多线程下,当无法给线程分配内存空间时,会抛出OutOfMemoryError异常。
方法区内存溢出:
方法区的内存溢出主要分为两部分:一部分为运行时常量池的溢出,也就是常量过多产生的内存溢出;和产生的类型数据过多产生的内存溢出,例如动态代理产生代理类,不断产生代理类,使方法区产生了内存溢出。
直接内存区:
通过Unsafe分配内存会直接会产生本地直接内存溢出异常。可以通过-XX:MaxDirectMemorySize设置大小。因为直接内存不属于虚拟机内存数据范围,所以直接内存产生的异常在heap dump文件中看不见明显的异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值