阅读笔记-Java内存区域划分

来源:互联网 发布:网络水军推广公司 编辑:程序博客网 时间:2024/05/12 21:50

对于Java开发人员来说,由jvm去管理内存的使用,我们不用关心内存的使用和释放,但是一旦出现内存泄露和溢出,如果我们不了解JVM是如何管理内存的,我们将无从下手。

1.运行时数据区域

JVM会在执行java程序过程中把它所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途和销毁及创建时间,有的随着虚拟机进程启动而存在,有些区域则依赖用户线程的启动和结束而建立销毁。

(1)程序计数器

程序计数器是一块较小的内存空间,它可以看做是当前线程所执行的字节码行号的指示器,在虚拟机概念模型里(仅仅是在概念模型里,各种虚拟机会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成。

由于Java虚拟机的多线程是通过线程轮流切换并分配处理器时间的方式来实现的,在任何一个确定的时刻,一个处理器的一个内核都只会执行一条线程中的指令,因此为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器。

这块内存也是JVM规范中没有规定任何OutOfMemoryError的情况。

(2)虚拟机栈

这块内存也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的时候都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息,每个方法从调用到执行完的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。

我们经常会把JVM内存划分为堆(Heap)和栈(Stack),这种分法比较粗糙,我们在这里先说说栈,指的就是虚拟机栈。

局部变量表存放了编译器可知的各种基本数据类型(8个基本类型),对象引用(引用类型,不等同于对象本身,只是一个指向对象地址的引用)和returnAddress类型(指向了一条字节码指令的地址)。

其中64位长度的long和double类型的数据占用2个局部变量控件,其余数据只占用1个。局部变量表所需的内存在编译器完成分配,在方法运行期间不会改变局部变量表的大小,在虚拟机对这个区域规定了两种异常:如果线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,如扩展时无法申请到足够的内存,则会抛出OutOfMemoryError异常。

(3)本地方法栈

这个区域与虚拟机栈的作用非常相似,他们之间的区别是虚拟机栈为虚拟机执行Java(字节码)方法服务,本地方法栈则是调用虚拟机使用到的本地方法服务,这里同样也会抛出StackOverflowError和OutOfMemoryError异常。

(4)堆

对于很多应用来说,堆是JVM管理的内存中最大的一块内存。这一块内存是被所有线程所共享的,它在虚拟机启动时创建。此区域唯一的目的就是存放对象实例,在Java虚拟机规范中的描述是:所有的对象实例及数组都要在堆上分配。但是随着JIT编译器的发展与逃逸分析技术的逐渐成熟,栈上分配,标量替换优化技术将会导致一些微妙的变化发生,所有对象都分配在堆上这句话就不那么绝对了。

堆是GC(垃圾回收器)的主要管理区域,从内存回收的角度看,由于现在收集器基本都采用分代收集算法,所以Java堆还可以细分为新生代和老年代;还可以再细一点:Eden空间,FormSurvivor空间,To Survivor空间等。堆的划分目的主要是:与存放内容无关,为了更好的回收内存,或者更快的分配内存。

我们可以通过jvisualvm工具(Java自带分析工具)查看我们的堆内存使用情况(下一篇文章的主要讲解内容)


(5)方法区

首先这块内存也是所有线程共享的一块区域,这里存储的内容就是我们熟知的类信息,常量,静态变量,即时编译后的代码等。Java虚拟机规范把此区域描述为堆的一个逻辑部分,但是他有个别名叫做:Non-heap(非堆)。

对于习惯了在HotSpot虚拟机上开发的开发者来说,我们更愿意把它称作“永久带”(Permanent Generation),本质上两者并不等价,但是HotSpot把GC的分代收集扩展至了方法区,这样就省掉了单独写一个专门为这部分的内存管理代码。但是者更容易遇到内存溢出异常(OutOfMemoryException,永久带有-XX:MaxPermSize的上限)。因此HotSpot虚拟机的官方在JDK1.7的HotSpot中,已经把原本放在永久带中字符串常量池移出(采用Native Memory)来实现方法区的规划。

GC在这一部分的内存比较少出现,主要是卸载类型和针对常量池的回收。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

(6)运行时常量池

运行时常量池是方法区的一部分,Class文件中除了有类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内存将在类加载后进入方法区的运行时常量池中存放。

这块内存并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,例如:String的intern方法,由于运行时常量池是方法区的一部分,所以也受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

(7)直接内存

直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这块内存也被频繁使用,而且也可能导致OutOfMemoryError异常。

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

本机直接内存显然不会受到Java堆内存的限制,但是还是会受到本机总内存大小的限制。



0 0
原创粉丝点击