JVM运行时数据区域

Java和C不一样,C是程序员自己进行内存分配和垃圾回收;而Java是由虚拟机进行内存分配和垃圾的自动回收,不需要程序员手动进行。但如果不了解JVM划分的每块内存的大小及其作用,当出现内存溢出时就无从下手了。下面让我来描述一下JVM运行数据区的划分及作用。

首先,看下面一张图

从这张图上我们可以清楚的看到,JVM运行时数据区划分为5块区域,其中绿色区域为线程私有的,白色区域为所有线程共享的。下面阐述一下每块的作用。

一、程序计数器(Programer Counter Register)

程序计数器是JVM运行时数据区划分内存最小的一块区域,主要作用:当前线程所执行的字节码的行号指示器。

这块要怎么理解呢?

你肯定听说过java多线程,当一个线程得到cpu的调用,就可以执行当前线程的代码。由于cpu内部切换是非常快的,此时当另外一个线程得到cpu的调用,去执行其他线程的代码了。过了一段时间后,当原来的线程重新得到cpu的调度时,如果记住上一次执行到什么地方了呢,此时当前线程的程序计数器就发挥作用了。告诉cpu上一次执行到这里了,现在可以从这里继续执行了。其实就是这么个作用。

如果线程正在执行的是一个java方法时,程序计数器记录的是正在执行的字节码指令的地址。当线程正在执行的是native方法时,程序计数器记录的值为空。程序计数器是JVM运行时数据区唯一一个没有OutOfMemoryError的区域。

二、虚拟机栈(JVM Stack)

虚拟机栈描述的是Java方法的内存模型。当java方法被执行时,就会创建一个响应的栈帧(Stack Frame),主要用来存放局部变量表、操作数栈、动态链接和方法出口等信息。当java方法被执行时,进行压栈操作,具有“先进后出”特性。

很多人粗略地将JVM运行时数据区分为堆区和栈区,所以可以看出这部分的重要性了。

局部变量表主要用来存储编译后的基本数据类型(字节类型、短整型、字符类型、整型、长整型、单精度类型、双精度类型、布尔类型)、对象引用类型(类、接口和数组)和ReturnAddress等信息。

局部变量表当然是存储局部变量的地方,其中64位长度的long和double会占用两个局部变量空间,其余的只会占用一个。

当我们要访问的栈帧深度大于当前线程栈的最大深度时,就会出现StackOverFlowError。当栈自动扩展时没有获取到足够的内存导致失败时,会出现OutOfMemoryError。

三、本地方法栈(Native Method Stack)

本地方法栈与虚拟机栈相似,只是native方法的内存模型而已。

四、堆(Java Heap)

堆是JVM运行时数据区分配内存最大的一块区域。主要用于存放生成的对象实例。同时也是垃圾回收器最长光顾的地方,所以又称为“GC”堆。

由于现在的垃圾回收都采用分代回收的算法。因此,java堆又可分为新生代和老年代,更详细的可分为Eden空间、From Survivor空间和To Survivor空间。堆是多线程共享的区域,因此,在堆中也可能存在多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。无论怎么划分,都与存储的内容无关,仅是为了提高内存分配和垃圾回收的效率。

java堆可以使用不连接的内存空间,只要在逻辑上连续即可。因此,可以为堆分配固定的大小或者可扩展的区域。

当创建的实例对象过多,超过了java堆的内存容量时,就会发生OutOfMemoryError。

五、方法区(Mehod Area)

方法区与堆一样,也是线程共享的。主要存储JVM加载的类信息、常量、静态变量和即时编译器编译后的代码等。方法区也叫做“非堆”区,主要用来与堆进行区分。

方法区与堆一样,也可以使用空间上不连续的空间。

用的人喜欢把方法区称为永久代。其实这两个概念并不是一回事,主要是因为HotSpot将GC回收扩展到了方法区,或者说使用永久代实现了方法区,这样HotSpot就可以像管理java堆内存一样管理方法区了,省去了为方法区编写GC处理代码的麻烦。想其他的虚拟机是不存在永久代的概念的。JDK1.7往后已经开始摒弃永久代了。同时,已经开始将字符创常量池移出常量池了。

方法区内存回收主要是针对常量池的回收和类的卸载。

方法区也会抛出OutOfMemoryError异常。

六、运行时常量池(RunTime Constant Pool)

运行时常量池(RunTime Constant Pool)是方法区的一部分。Class文件除了包括类的版本、字段、方法和接口等描述性信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译器生成的字面量和符号引用。这部分内容将在类加载后进入到方法区的常量池中进行存放。

JVM对类的每一个字节类型都有严格的定义,只有符号虚拟机规范的才能进行加载和运行。但是并未对运行时常量池作出严格的规定,需要由各自的虚拟机厂商实现。

运行时常量池与Class文件的常量池的另一个区别在于动态性。java语言并不要求常量在编译器就已经产生,也可以在运行时动态产生,例如String类的intern()方法。

既然运行时常量池是方法区的一部分,当然也会受到内存管理的限制,当无法申请到足够的内存时,也会抛出OutOfMemoryError异常。

七、直接内存(Direct Memory)

这部分估计很多人都没听过,这部分并不是JVM运行时数据区的一部分,也不是java虚拟机规范中定义的内容。但是这部分经常会使用,也会导致OutOfMemoryError异常。所以这里一同讲解。

在JDK1.4后出现了NIO,是一种通过管道(Channel)和缓冲(Buffer)实现的一种I/O,它可以使用Native函数库直接分配堆外内存,通过一个存储在堆内的DirectByteBuffer对象对这块内存进行操作。

直接内存并不受java堆大小的限制。当各个区域的内存总和大于本机的物理内存时,就会抛出OutOfMemoryError异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值