JAVA 虚拟机深入研究(三)——Java内存区域

JAVA 虚拟机深入研究(一)——关于Java的一些历史

JAVA 虚拟机深入研究(二)——JVM虚拟机发展以及一些Java的新东西

JAVA 虚拟机深入研究(三)——Java内存区域

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

Java运行的时候会把内存分为若干个,他们各有各的用途,每块区域的创建和销毁都是相对独立的,有的跟虚拟机一起混,有的则抱着用户的大腿同生共死。

按照第七版的《Java虚拟机规范》规定,JVM所管理的内存包括以下几个区域。

1.1 程序计数器(Program Counter Register)

这块内存区比较小,按照概念模型(各种虚拟机可能不按照概念模型,而采用自己的更高效的方式来搞定),字节码解释器就通过这个计数器来寻去下一个字节码指令,分支,循环,跳转,异常处理,线程回复等都需要这个东西来完成。

Java虚拟机多线程采用时间片轮转的方式,所以同一时刻,一个内核只能执行一个线程中的指令。因此,为了当获取到时间片之后线程可以正常恢复执行,每个线程都需要有自己的线程计数器,这种内存称为“线程私有”的内存。

如果线程执行的是Java方法,这个计数器就存储正在执行的字节码指令地址,如果线程执行的是Native方法,那么这个计数器就是空的(undefined)。

这个内存区域是唯一一个在Java虚拟机规范中没有规定任何OOM(OutOfMemoryError)情况的区域。

1.2 Java虚拟机栈(Java Virtual Machine Stacks)

跟计数器一样,这个区域也是线程私有的,跟线程同生共死。这个区域描述了Java方法执行的内存模型:没个方法执行的时候创建一个栈帧(回头再讲),存储局部变量表,操作数栈,动态链接,方法出口等信息。一个方法开始执行,栈帧就会在虚拟机栈中入栈,方法执行完成后出栈。Java虚拟机规范中规定,如果线程请求的栈深大鱼虚拟机允许的深度,就跑出StackOverFlowError,如果虚拟机栈允许动态扩展(多数都能,也可以固定),那就扩展,如果扩展的时候发现内存不足,跑出OutOfMemoryError。

有的人会把Java的内存草草的分为两部分,一部分叫堆,另一部分叫栈,这个分法太粗糙,实际上要复杂很多,不过其中的栈指的就是这个虚拟机栈里面所说的局部变量表。

局部变量表存储了基本数据类型(八种),returnAddress类型(指向一个字节码指令地址)和引用类型,引用类型包含两种种,第一可能是指向对象起始位置的引用指针,第二可能是指向一个代表对象句柄或者其他与这个对象相关的位置。

除了两个64位的数据类型(long,double)占用两个局部变量空间(Slot),其余的都只占用一个。局部变量表的内存空间在编译器就已经分配完成了,运行时不会改变局部变量表的大小。

1.3 本地方法栈(Native Method Stack)

跟虚拟机栈功能类似,区别在于这个栈服务于Native方法,而虚拟机栈服务于Java方法(字节码)。规范中没有对这个区域的实现做出明确的要求,所以相应的这个区域的实现就显得比较自由,像HotSpot就很干脆的直接和虚拟机栈合二为一。

1.4 Java堆(Java Heap)

多数的应用中堆都是虚拟机中最大的一块内存,按照虚拟机规范要求,所有的对象实例以及数组都要在堆上分配,随着JIT编译器的发展以及逃逸分析技术的成熟,栈上分配、标量替换优化技术就会带来一些新的变化,所有的对象都分配在对上就不是那么绝对了。

Java的堆是垃圾回收的大头,因此有时候会称为GC堆。由于现在多数GC都采用的是分代垃圾回收算法,所以对堆还可以细分,比如新生代,老年代。要是再详细一点,就有Eden空间,From Survivor,To Survivor等(前面提到过)。

规范中并没有要求堆在内存上的物理连续性,只要保证逻辑连续即可,设计实现的时候可以固定大小,也可以扩展,现在主流的都可以扩展(利用-Xmx和-Xms控制),如果碰到扩展时内存不够的情况,就会抛出OutOfMemoryError。

1.5 方法区(Method Area)

这个区域也是线程共享的区域,用于存储已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。规范中对这个区域的描述是,方法区是堆的一部分,但是它有个名字叫做非堆(Non-Heap),目的是用于区分堆。

有些在HotSpot上搞事情的人喜欢吧方法区和永久代(Permanent Generation)画等号,但是从本质上说这两个不是一回事,只不过HopSpot虚拟机的团队选择用永久代来实现方法区而已,省掉了为方法区单独编写内存回收代码的工作。

原则上,方法区的实现不受规范约束,但是从设计角度说,这个设计并不是好设计,由于方法区占用了永久代,导致永久代更容易出现内存溢出问题(永久代有最大内存先知 -XX:MaxPermSize),有一些方法,比如String.intern(),会因为这个原因而导致不同的虚拟机出现不一样的表现,目前JDK1.7中字符串常量池已经从永久代移出了。

方法区无法满足内存分配需求时,将会抛出OutOfMemoryError。

1.6 运行时常量池(Runtime Constant Pool)

这个部分是方法区的一部分。Class文件里除了有版本,字段,方法,接口等描述信息之外,还有一个是常量池(Constant Pool Table),用于存放在编译期生成的各种字面量和符号引用,并在类加载后进入方法区的常量池。

Java虚拟机规范对于Class的格式有严格要求,唯独对这个运行时常量池没有细节要求,不同的提供商可以按照自己的需要来实现这块区域。

这个运行时常量池具备动态性,Java并没要求常量只能在编译期产生,运行时也可以进入常量池,典型应用就是String.intern().

由于常量池就在方法区内部,因此也受到内存的限制,一旦申请不到内存就OutOfMemoryError。

1.7 直接内存(Direct Memory)

Java虚拟机规范中并没有对这个部分作出规范。

JDK 1.4中新加入了NIO,利用它可以使用Native函数库直接分配内存,然后通过一个存在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样,在一些场景中可以显著地提高性能,可以避免在Java堆中和Native堆中来回复制数据。

时间: 2024-12-29 01:40:47

JAVA 虚拟机深入研究(三)——Java内存区域的相关文章

Java虚拟机系列(三)---内存溢出情况及解决方法

因为Java虚拟机内存有堆内存.方法区.虚拟机栈.本地方法栈和程序计数器五部分组成,其中程序计数器是唯一一块不会发生内存溢出异常的内存区,所以只有四类内存区可能发生内存溢出异常,其中虚拟机栈和本地方法栈都是Java方法执行的内存模型,所以它们的异常发生情况几乎相同,另外,在方法区中.又有一块内存是常量池,所以内存溢出的情况可分为Java堆溢出.虚拟机栈和本地方法栈溢出.方法区和运行时常量池溢三种情况. 一.Java堆溢出 1.产生的原因:因为堆中存放的是对象实例和数组,所以当对象数量>最大堆容量

java虚拟机学习(一) 内存区域

java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个区域,包含方法区域,堆,虚拟机栈,本地方法栈,程序计数器,其中方法区域和堆是所有线程共享的数据区.结构如图: 程序计数器: 占的空间较小,可以看作是字节码行号指示器,字节码解析器是通过改变它的值来选取下一条字节码指令, 分支,循环,跳转,异常处理,线程恢复等 ,都依赖它来完成.每一条线程都有独立的一个计数器,也可以看作是私有内存,如果执行本地方法,这个计数器则为空, 该区域是唯一的一个没有OutOfMemory情况的区域. J

java虚拟机学习(三) 内存溢出异常

java 堆溢出: 在eclipse中测试时,可以在Debug/Run中设置虚拟机参数,比如-xmx 20M 代表虚拟机堆内存大小最大值是20M,-xms是最小堆内存.然后写个死循环测试类不断在List集合中添加对象, 当堆内存超出20M ,会报OutOfMemory异常. 虚拟机栈和本地方法栈溢出: 一个线程中如果,方法的深度超过了虚拟机允许的深度 ,会报StackOverFlow异常,比如递归调用方法.这个异常容易去实现,另一种是java虚拟机栈在扩展时如果无法申请到内存,则会报OutOfM

【深入Java虚拟机】之一:Java内存区域与内存溢出

[深入Java虚拟机]之:Java内存区域与内存溢出 内存区域 Java虚拟机在执行Java程序的过程中会把他所管理的内存划分为若干个不同的数据区域.Java虚拟机规范将JVM所管理的内存分为以下几个运行时数据区:程序计数器.Java虚拟机栈.本地方法栈.Java堆.方法区.下面详细阐述各数据区所存储的数据类型. 程序计数器(Program Counter Register) 一块较小的内存空间,它是当前线程所执行的字节码的行号指示器,字节码解释器工作时通过改变该计数器的值来选择下一条需要执行的

java虚拟机-(二)-java内存区域与内存溢出异常

1.简述:java虚拟机在执行java程序的过程中,会把他所管理的内存分为以下几个区域, 1.1.程序计数器 1.2.虚拟机栈 1.3.本地方法栈 1.4.java堆 1.5.方法区 如图所示: 2.各个区域的主要功能介绍 2.1.程序计数器:它可以看成是当前线程所执行的字节码的行号指示器,在虚拟机的概念模型中,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,   分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖计数器来完成,程序计数器是线程私有的内存,各线

深入理解Java虚拟机读书笔记---运行时数据区域

运行时数据区域 1.程序计数器 程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器.字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支.循环.跳转.异常处理.线程恢复等基础功能都需要依赖这个计数器来完成.由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令.因此,为了线

深入java虚拟机(一)——java虚拟机底层结构详解

在以前的博客里面,我们介绍了在java领域中大部分的知识点,从最基础的java最基本语法到SSH框架.这里面应该包含了在java领域里面的大部分内容了吧.但是,那些知识点是让我们从一个应用的层面上了解了java,java程序真正底层的运行机制和一些底层虚拟机的工作我们还不了解,虽然这些内容在我们真正的开发中几乎用不到这些底层的东西,但对于我们对java的理解会有比较大的帮助.尤其也对以后java开发中的性能优化有很大帮助,可以使我们减少一些没必要的内存浪费等好处.所以,从今天开始,我将和大家一起

深入Java虚拟机(1)——Java体系结构

Java体系结构 Java体系结构包括四个独立但相关的技术: 1.Java程序设计语言 2.Java class文件格式 3.Java应用编程接口(API) 4.Java虚拟机 当编写并运行一个Java程序时,就同时使用了这四种技术.用Java程序设计语言编写源代码,把它编译成Java class文件,然后在java虚拟机中运行class文件.当编写程序时,通过调用实现了Java应用编程接口(API)类中的方法来访问系统中资源如io.网络内存等.当程序运行的时候它可以调用class文件中的实现的

java虚拟机jvm启动后java代码层面发生了什么?

java虚拟机jvm启动后java代码层面发生了什么? 0000 我想验证的事情 java代码在被编译后可以被jdk提供的java命令进行加载和运行, 在我们的程序被运行起来的时候,都发生了什么事情, 下面就来探究下这个问题, 这个问题被拆成了两个问题, 第一个问题用来确定发生了哪些事情, 第二个问题用来确定这些事情是如何进行的. java进程里面都发生了哪些活动? 这些活动在java代码(反编译或者是源码)级别有所体现吗? 0001 寻找验证的方式 当我在探究上面两个问题时, 我想了很多方式去