Java内存区域与内存溢出异常

java与C++之间有堵由内存动态分配、垃圾收集技术所围成的墙,墙里的人想出来,墙外的人想进去。。

Java内存区域与内存溢出异常

 

如上图所示,我们下面就来具体介绍以下java虚拟机的以下几个运行时数据区域

 

一、程序计数器

       可以看作是当前线程所执行的字节码的行号指示器。线程私有

       字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码命令。它是程序控制流的指示器。具体到而分支、循环、跳转、异常处理、线程恢复等基础功能都是需要依赖这个计数器来完成。

       程序计数器是存在于线程之中的。就Java多线程的实现方式而言,需要线程轮流切换、分配处理器的执行时间

在任何一个确定的时刻,一个处理器都只会执行一条线程中的命令。因此,为了线程切换后能恢复到正确的执行位置

每条线程都需要一个独立的程序计数器,使得各条线程中的计数器互不影响,独立存储,我们就叫这类内存为“线程私有”的内存。

       如果线程正在执行的是一个Java方法,那么程序计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地Native方法,这个计数器值则为空。此内存区域是唯一一个在java虚拟机规范中没有规定任何内存溢出异常情况的区域。

 

二、Java虚拟机栈

       --- 存储方法执行时的相关数据,存储单元:栈帧 ;存储过程:从入栈到出栈 线程私有

       虚拟机栈的生命周期同线程相同,虚拟机栈描述的是java方法执行的线程内存模型:每个方法被执行的时候,java虚拟机都会同步创建一个栈帧,用于存储局部变量彪、操作数帧、动态连接,方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应这个一个栈帧在虚拟机栈中从入栈到出栈的过程。

       我们通常所说的栈内存,大多数就是指的虚拟机栈,更多情况下指的是虚拟机栈中的局部变量表部分。

       局部变量表存储了java编译期间的各种基本数据类型、对象引用类型,他不同于对象本身,可能是一个对象的引用,也可能是指向一个代表对象的句柄或与此对象相关的位置,和指向了一条字节码指令的地址。以上所说的这些数据类型在局部变量表中的存储空间以局部变量槽来表示,其中64位长度的lang和double类型的数据占用两个变量槽,其余数据类型占用一个。局部变量槽所需的存储空间在编译期就已经确定,并完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的内存空间时完全确定的,在方法运行期间不会改变局部变量表的大小。这里所说的大小指的是变量槽的数量,虚拟机实际上使用多大的存储空间来实现一个变量槽,这完全是由具体的虚拟机实现自行决定的事情。

 

三、本地方法栈

与虚拟机栈作用相同,区别在于本地方法栈为虚拟机使用到的本地方法服务。Java虚拟机栈为虚拟机执行的java方法服务。

 

        四、Java堆

               此内存区域是被所有线程共享的一块内存区域,在虚拟机启动时创建。唯一的目的--- 存放对象的实例

     “所有的对象实例和数据都应当在堆上分配”,这是之前对堆的认知。但是随着即时编译技术的进步,以及逃逸分析技术的日益强大,栈上分配、标量替换等优化手段正在冲击着这个论点,所以说,所有的对象实例和数据都应当在堆上分配的结论也就不是那么绝对了。

        从分配内存的角度上来说,所有线程共享的java堆中可以划分除多个线程私有的分配缓冲区,以用来提升对象分配时的效率。不过无论从什么角度,如何划分,都不会改变java堆中存储内容的共性--- 对象的实例。

        java堆即是可以被实现成固定大小的,也可以是可扩展的,不过当前主流的java虚拟机都是按照可扩展来实现的。

通过参数(-Xmx 、-Xms)设定。如果在java堆中没有内存完成实例分配,且堆也无法再进行扩展是,java虚拟机将会抛出内存溢出异常!

 

五、方法区

       与堆一样,是各个线程的共享内存区域。用于存储以被虚拟机加载的类型信息、常量、静态变量、即时编译后的代码缓存等数据。别名--非堆。

       在jdk8时,HotSpot虚拟机已完全废弃永久代,改用在本地内存中实现的元空间。之前永久代中的信息,包含:字符串常量、静态变量、类型信息全部移入到元空间中。这个区域的垃圾回收主要是针对常量池的回收+对类型的卸载。

当方法区无法满足新的内存分配需要时,就会抛出内存 溢出异常!

 

六、运行时常量池

      方法区的一部分,并受到方法区内村的限制。 用于存放编译期生成的各种字面量有符号的引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

运行时常量池相对于Class文件常量池的区别在于:具备动态性,就是说,在运行期间也可以将新的常量放入池中,例如String类的intern()方法。

 

七、直接内存

       使用Native函数库直接分配堆外内存,然后通过一个存储在java堆里面的DirectByteBuffer 对象作为这块内存的引用进行操作。避免了在java堆中和Native堆中来回复制数据。直接内存受限于计算机各个内存区域总和与物理内存的大小。