内容简介:浅析Java内存区域
内存管理是开发者必须掌握的基本功,不然程序总是会在各种难以捉摸的错误中崩溃,一些语言,例如C、C++开发者们自己申请内存,使用完自己释放,但是不当的代码书写习惯往往导致内存泄露,引用空指针等等错误,而 Java 借助于虚拟机帮我们完成了许多工作,使开发者从内存管理的深坑中爬出来了,但是由于隔着这层虚拟机,出现问题时的应对策略更显功力,需要对虚拟机内存管理机制的深入了解。
这篇文章只是粗浅的介绍下虚拟机内存区域的大概分布,让初学者在脑海中有个大概印象,而印象的开始则借助于下面的一幅图:
Java内存区域分为大的两个区域,一部分是线程共享的,另一部分则是每个线程所独有的。刚开始了解编程时,大致就有印象,栈内存存在于方法体中,定义的那些变量什么的都是栈内存,方法结束就没了,堆内存则是使用new关键字申请出来的。当然这只是粗线的认识,下面一块块的说上图中的内存分布。
程序计数器
类似于CPU中的PC寄存器,用于存放下一条指令的地址,但是虚拟机不使用CPU的程序计数器,而是自己在内存里设立一片区域模拟CPU的程序计数器。改变计数器的值来选取下一条需要执行的字节码指令,包括分支、循环、跳转、异常、线程恢复等基础功能都依赖于计数器。
Java的每个线程都有其独立的计数器,计数器之间互不影响,这样当多线程操作时,一个挂起的线程在恢复时,仍然能够从计数器中恢复之前运行到的地方,继续执行。所以说程序计数器也是线程隔离的。执行Java方法时,计数器中存放的是虚拟机字节码的地址,而运行Native方法时则是空(undefined)。该区域不会产生OutOfMemoryError。
虚拟机栈
Java虚拟机栈也是线程私有的,它的生命周期等同于线程的生命周期。虚拟机栈描述的是Java方法执行时的内存模型。当方法执行时,会创建一个栈帧,用于存储方法执行期间所用到的数据结构,包含 局部变量表,操作数栈,动态链接,方法出口 等信息。
当一个方法执行时,一个包含以上元素的栈帧入栈,当方法退出时,栈帧出栈。一般我们都会知道内存区分为堆区和栈区,实际上也是个粗浅的分法,栈指的就是虚拟机栈,而其中最重要的部分就是局部变量表。
Java的垃圾收集器是不会去回收栈上的内容的,因为栈上的内容总是随着方法的结束自动释放。局部变量表包含着各种编译期已知的 基本数据类型 、 对象引用 和 returnAddress 。基本数据类型就是Java的8大基本数据类型(boolean,byte,char, short, int, float, long, double),对象引用,你可以把它当成指向实际对象地址的指针或者一个代表对象的句柄,returnAddress则是一条字节码指令的地址。当进入一个方法时,它所需要分配的空间在编译期就是已知的了。
在虚拟机栈中可能会报以下两种异常:
- StackOverflowError: 线程请求的栈深度大于所允许的深度
- OutOfMemoryError: 大多数虚拟机栈是可以动态扩展的,如果无法申请到足够内存,就会抛出。
本地方法栈
区别于虚拟机栈执行的是Java方法,本地方法栈则是虚拟机使用的Native方法服务,我们在看一些库的源码时正常定位到最后就是用Native方法实现的,但是在虚拟机规范里对本地方法使用的语言,使用方式进行硬性规定,所以虚拟机可以任意实现它。HotSpot中本地方法栈和虚拟机栈是合在一起的。
堆
至少从学习 C语言 时,我们就听说malloc方法会在堆上分配空间。Java的堆上也是分配实例对象的。虚拟机规范上讲,基本所有的对象实例和数组都分配的堆上,例如上面栈上对象引用指向的对象,都是在堆上分配的。但是随着编译器技术的发展,所有对象都在堆上分配就不是那么纯粹了。
堆也是垃圾收集(GC)的主阵地。现代收集器基本都采用了分代收集算法,所以堆也可以划分为新生代和老年代,再细致点还有Eden空间,From Survivor空间和To Survivor空间。由于虚拟机实现了自动垃圾收集,所以在Java中,堆中new出的对象是不需要手动释放的。
我们可以通过 -Xmx
和 -Xms
控制堆的默认大小,如果在堆中再也申请不到内存,则会抛出 OutOfMemoryError
异常。
方法区
方法区也是各个线程间共享的区域,一般存储已被加载的 类信息、常量、静态变量、即时编译器编译后的代码 等数据。一般很多人也把方法区称为永久代。其实仅仅是HotSpot团队把GC分代收集也扩展到方法区了,让垃圾收集器可以一块回收方法区的内存,但是其它的虚拟机实现没有这么做,也不存在永久代的,HotSpot自身也已经在JDK1.7上移除了永久代中的常量池。
相对而言,垃圾收集在方法区是不怎么出现的。这个区域主要回收的是常量池和类型的卸载,但是实际上对二者的回收发生的条件极为苛刻,很少发生收集。
运行时常量池是方法区的一部分,Class文件中包含常量池信息,用于存放编译期生成的各种字面量和符号引用,这部分在类加载后进入方法区的运行时常量池。这部分可以参考我的: 深入理解JVM类文件格式
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Go内存管理源码浅析
- 内存四区模型浅析——C语言
- Linux 物理内存外碎片化浅析
- 浅析 Linux 的共享内存与 tmpfs 文件系统
- SQL Server Sleeping会话占用内存资源浅析?
- 【V8引擎】浅析Chrome V8引擎中的垃圾回收机制和内存泄露优化策略
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。