java数据区_Java运行时数据区域介绍

前言

相信学习过C/C++的朋友们肯定对指针这个概念印象深刻,有了指针,我们就可以随意操作分配给程序的这一块内存,此时此刻内存就是恕瑞玛,程序员就是黄鸡,可以主宰内存里的一切。

但与此同时,权力的代价就是我们需要花费更多的精力在内存管理上,规定某些变量何时生何时死。很明显,这大大增加了一个大型的C/C++系统中程序员的工作量。

在Java中,指针这个概念被删除了,和它有着类似作用的是一个叫做引用的东西,同时引入了Java虚拟机(Java Virtual Machine),这可是个好东西,有了JVM后,程序员不用在内存管理上再亲历亲为了,内存泄露和溢出的问题也减少了很多(计算机永远不会错!)

但是问题的减少不代表问题就此消失,如果不了解JVM的话可能无法很好的排查错误,所以用这一篇文章来介绍一下JVM内存中的各个区域

下面会一个个介绍这些区域的作用

程序计数器 PCR

程序计数器其实就是字节码解释器工作时使用的一个标志,它标志了当前进行到哪个行号了,是程序控制流的指示器。在Java多线程开发的时候,每个线程都分配了一块内存,不同线程之间来回切换,为了从A线程切换到B线程再切换回A线程时可以定位到之前执行的位置,每个线程都被分配了一个PCR。这个内存区域不会有任何OutOfMemoryError的情况。如果线程执行一个Java方法,那么PCR记录的时虚拟机字节码的地址,如果是本地方法,那么计数器的值为Undefined

Java 虚拟机栈 VM Stack

Java虚拟机栈也是线程私有的,它描述的是Java方法执行的线程内存模型,在我们使用.run方法的时候,不同线程之间都有自己的一个栈帧用来存储局部变量表,操作数栈,动态连接,方法出口等,这块学过计算机系统的朋友应该都很熟悉了。

VM Stack中存放了编译期可知的各种基本数据类型,对象引用和returnAddress,这些数据类型在局部变量表以局部变量槽表示。局部变量表所需要的内存空间在编译期完全确定,也就是说,进入一个方法后,这个方法需要在它的栈帧中分配多大的局部变量表是完全确定的。这里的“大小”值的是变量槽的数量,虚拟机最后使用了多大的空间因不同版本的虚拟机而异。

这个内存区域会发生两类异常,一个是线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError,如果Java虚拟机栈容量允许动态扩展,当拓展到极限的时候会抛出OutOfMemoryError

本地方法栈

本地方法栈和VM Stack的作用基本相同,区别在于VM Stack是为Java方法服务,本地方法栈为本地方法服务,也会有上述的两种异常。

Java 堆

Java堆是虚拟机所管理的内存中最大的一块,被所有线程共享,这个区域存在的唯一目的就是存访对象的实例,Java程序中几乎所有的对象实例都在堆上分配内存。不过由于技术(即时编译,逃逸分析,标量替换)的进步,可能以后并不是所有的Java对象实例都会在堆上被分配。

同时,Java堆还是GC(Garbage Collected)管理的内存区域,由于现代垃圾收集器大部分都是基于分代收集理论设计的,所以java堆中经常出现“新生代”,“老年代”等名词。

从分配内存的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer)来提升对象分配时的效率。

TLAB的能够让每个Java线程能使用自己专属的分配指针来分配空间,均摊对Java堆里共享的分配指针做更新而带来的同步开销,这个内容会用一篇新的文章来讲,这里就不继续展开了。

但是无论如何划分,Java堆中存储内容的共性不会被改变,无论哪个区域,存储的都是对象实例,划分是为了更快的分配内存和更好的回收。

Java堆可以处于物理上不连续的内存空间中,但是逻辑上它是连续的,就像磁盘一样,但是对于体积较大的对象,从效率和简单的角度出发,虚拟机会将它们放在连续的空间中,这里涉及到Cache的内容,计算机专业的朋友对这个应该比较熟悉。

在主流的JVM中,Java堆的大小是可拓展的,可以通过参数-Xmx和-Xms设置,当堆无法再扩展且还有对象实例需要分配的时候,JVM会抛出OutOfMemoryError异常。

方法区

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

对于HotSpot虚拟机来说,在JDK 6之前,运行时常量池是方法区的一部分,同时方法区里面存储了类的元数据(变量名,方法名,访问权限等),即时编译器编译后的代码(比如spring 使用IOC或者AOP创建bean时,或者使用cglib,反射的形式动态生成class信息等)等。

JDK 7之后,运行时常量池就被移出去了。到了JDK 8,HotSpot完全废除了方法区上的永久代,(JDK 8以前,HotSpot将GC使用永久代来实现方法区,因为容易造成内存溢出),在本地内存中实现了元空间,把JDK 7中永久代还剩余的内容(主要是类型信息)全部移动到了元空间中

元空间使用的是与堆不相连的本地内存区域,所以理论上系统内存有多大,元空间就有多大,不会出现永久代存在时的内存溢出问题。后面也会专门用一篇文章来介绍元空间的。

运行时常量池

Class文件中有一项是常量池表,用来存放编译器生成的各种字面量与符号引用,字面量包括了文本字符串,final,基本数据类型和其他,符号引用包括了类和结构的完全限定名,字段,方法的名称和描述符等。

这部分内容将会在类加载后存放到方法区的运行时常量池中。运行时常量池相对于Class文件常量池具有动态性,并非预置入Class文件常量池的内容才能进入运行时常量池,运行期间的新的常量也可以,一个栗子就是String类的intern()方法

直接内存

在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。

参考[1] 深入理解Java虚拟机,周志明

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值