java面试-JVM内存区域划分

JVM内存划分

说到Java内存区域,刚开始接触java的人会下意识说出“堆栈”。这里要明确堆栈不是一个概念,而是两个概念,堆和栈是两块不同的内存区域,简单理解的话,堆是用来存放对象而栈是用来执行程序的。其次,堆内存和栈内存的这种划分方式比较粗糙,这种划分方式只能说明大多数程序员最关注的、与对象内存分配关系最密切的内存区域是这两块,Java内存区域的划分实际上远比这复杂。对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new操作去配对delete/free代码,不容易出现内存泄露和内存溢出问题。但是,也正是因为Java把内存控制权交给了虚拟机,一旦出现内存泄露和内存溢出的问题,就难以排查,因此一个好的Java程序员应该去了解虚拟机的内存区域以及会引起内存泄露和内存溢出的场景。

 

之所以要划分这么多区域出来是因为这些区域都有自己的用途,以及创建和销毁的时间。有些区域随着虚拟机进程的启动而存在,有的区域则依赖用户线程的启动和结束而销毁和建立。图中绿色部分就是所有线程之间共享的内存区域,而其余部分则是线程运行时独有的数据区域,从这个分类角度来看一下这几个数据区。

 

线程独占区域

 

PROGRAM COUNTER REGISTER,程序计数器

程序计数器是当前线程所执行的字节码的行号指示器,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令

在JVM规范中规定,如果线程执行的是非native方法,则程序计数器中保存的是当前需要执行的指令的地址;如果线程执行的是native方法,则程序计数器中的值是undefined。

由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。

总结:
当前线程所执行的字节码的行号指示器;
当前线程私有;
不会出现OutOfMemoryError情况。

JAVA STACK,虚拟机栈

Java栈也称作虚拟机栈(Java Vitual Machine Stack),也就是我们常常所说的栈,跟C语言的数据段中的栈类似。事实上,Java栈是Java方法执行的内存模型

每个线程每执行一个方法就会创建一个栈帧,并将其压栈,在方法执行完成后,将其出栈,栈的大小一般在256K~756K之间,取决于jvm的实现,当无法申请到足够的内存,或者是超过了jvm虚拟机允许的最大深度,就会出现栈溢出或者oom异常,接下来说明一下栈帧中各个变量的意义

  1. 局部变量表:用来存储方法中的局部变量,对于基本变量直接存放其值,对于引用类型的变量,则存放其引用
  2. 操作数栈:用于存放进行计算的值
  3. 指向运行时常量池引用:方法在运行时,可能会用到类中的常量,所以必须要有引用指向运行时常量
  4. 方法返回地址:在一个方法执行完成后,需要返回调用它的位置,这里记录了调用这个方法的位置

总结:
线程私有,生命周期与线程相同;
java方法执行的内存模型,每个方法执行的同时都会创建一个栈帧,存储局部变量表(基本类型、对象引用)、操作数栈、动态链接、方法出口等信息;
StackOverflowError异常:当线程请求的栈深度大于虚拟机所允许的深度;
OutOfMemoryError异常:如果栈的扩展时无法申请到足够的内存。

NATIVE METHOD STACK,本地方法栈

本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的。在JVM规范中,并没有对本地方发展的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。 

线程间共享区域

HEAP,堆

堆在虚拟机启动的时候创建,此内存的唯一的目的就是保存对象实例,为了对不同生命周期的对象进行管理,提升性能,现在的垃圾收集器都会采用分代的垃圾收集算法,所以堆也可以分为年轻代和老年代,再细分可以划分为Eden区、From Survivior区、To Survivor区

总结:

可以通过-Xmx和-Xms控制堆的大小;

OutOfMemoryError异常:当需要分配对象实例,但剩余的堆内存无法完成分配,并且无法在进行扩展的时候

METHOD AREA,方法区

块区域用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,虚拟机规范是把这块区域描述为堆的一个逻辑部分的,但实际它应该是要和堆区分开的。从上面提到的分代收集算法的角度看,HotSpot中,方法区≈永久代。不过JDK 7之后,我们使用的HotSpot应该就没有永久代这个概念了,会采用Native Memory来实现方法区的规划了。

总结:
线程间共享;
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;
OutOfMemoryError异常:当方法区无法满足内存的分配需求时。

RUNTIME CONSTANT POOL,运行时常量池

 上面的图中没有画出来,因为它是方法区的一部分。Class文件中除了有类的版本信息、字段、方法、接口等描述信息外,还有一项信息就是常量池,用于存放编译期间生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中,另外翻译出来的直接引用也会存储在这个区域中。这个区域另外一个特点就是动态性,Java并不要求常量就一定要在编译期间才能产生,运行期间也可以在这个区域放入新的内容,String.intern()方法就是这个特性的应用。

总结:
方法区的一部分;
用于存放编译期生成的各种字面量与符号引用;
OutOfMemoryError异常:当常量池无法再申请到内存时。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值