关于jvm内存区域的随笔

写在正式内容以前

        进入java的世界这么久了,也一直想写写博客记录自己的工作学习的收获,但是以前一直以为自己只有看博客的实力,没有写博客的实力。直到现在才下定决心开始写下自己的第一篇博客,算是自己学习的笔记吧。

        我去面试的时候,经常就被问到一个问题,你了解jvm的内存吗?以前虽然也看过这方面的内容,但是都是为了面试而看,感觉也不怎么透彻。那么我的第一份博客,就写给jvm的内存区域吧。

导语

       我们都知道,有助于jvm的自动内存管理机制,我们不再需要为每一个操作去写配对的delete/free代码,从而不易出现内存得泄露和溢出的问题。但是jvm也不可能完美的,那么当它一旦出现内存泄露和溢出,如果我们不是很了解jvm的内存管理机制的话,我们去解决这个问题的话,就会很艰难。

        在我们运行一个java程序会产生一个java进程,每个java进程可能包含一个或者多个线程,每一个Java进程对应一个JVM实例,而这个jvm实例在执行这个java程序时管理的内存划分为若干个不同的区域,在java1.7中jvm所管理的内存包含以下几个数据区域。

程序计数器

        程序计数器可以看做是当前线程所执行的字节码行号指示器,在虚拟机的概念模型里,字节码解释器工作是就是通过改变这个计数器的值来选取吓一跳需要执行的字节码指令,分支、循环、跳转、异常处理、线程回复语等基础功能都需要依赖这个计数器来完成。

        由于java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,为了线程切换能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器。所以这块区域是线程私有的。

        如果线程正在执行一个java方法,那么这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个native方法,那么这个计数器的值为空(Undefind)。此区域是唯一一个jvm规范中没有任何OutOfMemoryError情况的区域。

Java虚拟机栈

        与程序计数器一样,这块区域也是线程私有的,他的生命周期跟线程相同。虚拟机栈描述的是java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局布变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用到直至执行完成的过程,就对应一个栈帧在虚拟机中入栈到出栈的过程。

       java虚拟机栈中存放的局部变量表存放了编译器可至的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型)和returnAddress类型。局部变量表所需的内存空间在编译期间完成分配。

       在java虚拟机规范中,对这个区域规定了两周异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将会抛出StackOverflowError;如果虚拟机可以动态扩展,但是无法申请到足够的内存,就会抛出OutofMemoryError异常。

本地方法栈

        本地方法栈跟虚拟机栈所发挥的作用是相似的,不过虚拟机方法栈是为java方法服务,而本地方法栈是为Native方法服务的。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError。

Java堆

        一般来说,堆是jvm管理内存中的最大的一块,也是所有线程共享的一块,在虚拟机启动的时候创建。堆的唯一目的就是存放对象,但是呢,jdk 1.7后,移除了方法区间,运行时常量池和字符串常量池都在堆中。关于堆的细致内存分配、回收等细节,我将在下一篇笔记中细致的去写。

方法区

        方法区跟Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。很多人都更愿意把方法区称为“永久代”(Permanent Generation)。从jdk1.7已经开始准备“去永久代”的规划,jdk1.7的HotSpot中,已经把原本放在方法区中的静态变量、字符串常量池等移到堆内存中。在jdk1.8中,永久代已经不存在,存储的类信息、编译后的代码数据等已经移动到了元空间(MetaSpace)中,元空间并没有处于堆内存上,而是直接占用的本地内存(NativeMemory)。

运行时常量池

        运行时常量池在jdk1.7及以前,是方法区的一部分,在jdk1.7移除方法区后,运行时常量池放在了堆中,主要用于存放编译器生成的各种字面量(字符串、final常量)和符号引用。运行时常量池相对于Class文件常量池具备动态性,java语言并不要求常量只有编译器才能产生,也是并非预置入Class文件中的常量池才能进去运行时常量池,运行期间也可能将新的常量放入池中,比如String类的intern()方法。

猜你喜欢

转载自www.cnblogs.com/lxgogoBlog/p/11071288.html