ITKeyword,专注技术干货聚合推荐

注册 | 登录

JVM运行期的数据区域-入门篇

haydenwang8287 分享于 2010-12-08

推荐:jvm的几个运行时数据区域

     jvm的几个运行时数据区域  1.jvm的内部体系结构浅析       2.jvm的几个运行时数据区域       3.jvm的内存溢出异常 上一篇文章已经简单介绍了jvm的内部体系

2018阿里云全部产品优惠券(新购或升级都可以使用,强烈推荐)
领取地址https://promotion.aliyun.com/ntms/yunparter/invite.html

推荐:《深入理解JAVA虚拟机》——JVM运行时数据区域

JAVA在运行的时候,将管理的内存划分为不同的数据区域。如图: 程序计数器: 一块较小的内存空间,作为当前线程所执行的字节码的行号指示器。字节码指示器就是通

1 运行期的数据区域 jvm定义了各种运行期的数据区域,可以在执行程序时使用。有些数据区域在虚拟机启动时创建,当虚拟机中止时才被销毁。另外一些数据区域是单个线程的,单个线程的数据区域在线程被创建时创建,线程中止时销毁。   1.1 pc寄存器(pc register) jvm能够支持多个线程同时执行。每个java虚拟机线程都有独自的pc寄存器(程序计数器)。在任何时候,每个jvm线程执行单个方法的代码时,这个方法就是那个线程的当前方法。如果那个方法不是本地的(native),pc寄存器包含当前正在被执行的指令地址。如果线程正在执行的方法是本地的(native),jvm的pc寄存器是未定义的。jvm的pc寄存器有足够的宽度来持有一个返回地址(returnAddress)或在特定的平台上的本地指针(native point)。   1.2 java虚拟机栈(stacks) 每个jvm线程都有一个私有的jvm栈(stack),它将和线程同时创建。jvm栈存储帧(frames)。jvm栈类似于传统语言例如c的栈,它持有局部变量和部分结果并且参与方法的调用和返回。 由于jvm栈除了压入弹出帧外不会被直接操作,所以帧可以由堆(heap)来分配。对于jvm栈的内存不必是连续的。 jvm规范允许jvm栈的大小是固定的,也可以是根据需求计算来扩展和收缩。如果jvm栈是固定大小,则每个jvm栈大小可以在栈创建时独立地选择。一个jvm实现可以让程序员或用户控制jvm初始栈的大小,以及在动态扩展或收缩jvm栈时,控制其最大值和最小值。 以下异常情况与jvm栈有关:
  • 如果线程中的计算需要一个比允许的jvm栈更大时,jvm将会抛出StackOverflowError.
  • 如果jvm栈可动态扩展,当没有足够的内存分配给所尝试的扩展,或者没有足够的内存来为一个新线程创建初始化jvm栈,jvm将会抛出OutOfMemoryError.
  1.3 堆(heap) jvm有一个所有jvm线程间共享的堆(heap)。堆是分配所有类实例和数组内存的运行期数据区域。 堆在虚拟机启动时被创建。堆中对象的存储由自动存储管理系统(被称为垃圾回收器gc)回收。对象从来不会被显示的回收。jvm承担着非特殊类型的自动存储管理系统,当然存储管理技术也可以根据实现者的系统要求来选择。堆可以是固定大小或是根据需求计算进行扩展,或者也可以是当一个大的堆不必要时进行收缩。堆的内存不需要是连续的。 一个jvm实现可以让开发者或者用户控制堆初始的大小,同样的,如果堆能够动态扩展或者收缩,可以控制其最大值和最小值。 以下异常情况与堆有关:
  • 如果计算需求所须更多的堆无法由自动存储管理系统提供时,jvm将会抛出OutOfMemoryError.
  1.4 方法区(method area) jvm有一个所有jvm线程共享的方法区。方法区类似传统语言编译后代码的存储区,或者像是UNIX进程中的正文(text)段。它保存每个类结构诸如运行时的常量池、域、方法数据、方法和构造器的代码(包括在类、实例初始化、接口初始化时使用的特殊方法)。 方法区在虚拟机启动时被创建。虽然方法区逻辑上是堆的一部分,但是简单的实现可以选择既不垃圾回收也不压缩它。该版本的jvm规范不要求指定方法区的位置或者用于管理编译后代码的策略。方法区可以是固定大小,也可以根据需求计算扩展,并且当大的方法区不再需要时进行收缩。方法区的内存不需要是连续的。 一个jvm实现可以让开发者或用户控制方法区初始的大小,同样的,在可变大小方法区时,控制方法区的最大值和最小值。 以下异常情况与方法区有关:
  • 如果方法区的内存不能满足分配求情,jvm会抛出OutOfMemoryError。
  1.5 运行常数池(Runtime Constant Pool) 运行常数池是每个类或者接口运行时constant_pool表在类文件(class file)中的表现。它包含几种常数类型,范围从编译期已知的数字文字到必须在运行期被解析的方法和域引用。常数池的作用类似于传统编程语言中的符号表,尽管它比典型符号表有更宽的数据范围。 每个常量池由jvm方法区分配。每个类或接口的常量池在类和接口被jvm创建时建立。 以下异常情况与类或接口的常量池有关:
  • 当创建类或接口时,如果常量池的建立需要的内存不能被jvm的方法区分配,jvm会抛出OutOfMenoryError.
  1.6 本地方法栈(Native Method Stacks) 一个jvm的实现可以使用传统栈,俗称“C栈(C stacks)”,来支持本地方法,那些非java编程语言中的的方法。本地方法栈可以被jvm指令集的解释器实现(比如C)所使用。不装载本地方法和不自身依赖传统栈的jvm实现者,不需要提供本地方法栈。如果提供,本地方法栈通常由每个线程在创建时被分配。 jvm规范允许本地方法栈是固定大小或者根据需求动态扩展或收缩。如果本地方法是固定大小,每个本地方法栈大小在栈被创建时独自选择。在任何情况下,一个jvm实现可以让开发者或用户控制本地方法栈的初始大小。如果是可变大小本地方法栈,同样能够控制方法栈的最大值和最小值。 以下异常情况与本地方法栈有关:
  • 如果线程中计算所需的本地方法栈大于允许范围,jvm会抛出StackOverflowError。
  • 如果本地方法栈能动态扩展,当没有足够的内存分配给所尝试的扩展,或者没有足够的内存分配给新线程中创建的初始本地方法栈,jvm就会抛出OutOfMemoryError。
  2 帧(Frames) 帧用来存储数据和部分结果,同样也用来执行动态链接、返回方法值和投递异常。 每次调用方法时会创建一个新的帧。当方法调用完成时(无论正常或者异常中断抛出异常),帧才会销毁。帧由创建帧的线程的jvm栈分配空间。每个帧拥有自己的本地变量数组,操作数栈(operand stack)和当前类中方法运行常数池的引用。 局部变量数组和操作数栈的大小在编译时决定,并且该大小由与方法代码相关联的帧提供。因此帧数据结构的大小依赖于jvm的实现,帧内存在方法调用时被同时分配。 只有一个帧,即正在执行方法的帧,在给定线程控制的任何点是活跃的。这个帧被称为当前帧,这个方法即为当前方法。当前方法所在的类被定义为当前类。局部变量和操作数栈的操作通常引用当前帧。 只有当方法调用另一个方法或者方法结束时,帧不再为当前帧。当一个方法被调用,控制转移到新方法时,一个新帧被创建并成为当前帧。当方法返回时,如果方法引用有结果,则当前帧传递回该方法引用的结果给上一个帧。当前帧被抛弃,上一个帧成为当前帧。 注意一个线程中创建的帧是局部的,不能被其他线程所引用。   2.1局部变量(Local Variables) 每个帧都有一个局部变量数组。帧中局部变量数组的大小在编译时决定,由二进制表示的类或接口与帧相关联方法的代码提供。 一个单字节的局部变量能保存boolean、byte、char、short、int、float、引用或返回地址的值。两个局部变量能保存long或者double。 局部变量通过索引寻址。第一个局部变量的索引为0。本地变量数组的索引基于0与数组大小之间的整数。 long与double类型占据两个连续的局部变量。该值通过第一个变量的索引寻址。例如,一个double类型的值存储在局部变量索引为n(其实占据了局部变量n和n+1的索引),但本地变量索引n+1不能被使用。它可以存储到,但是,这样会使本地变量n的内容无效。 jvm不需要n为偶数。在直观上来讲,类型double和long在局部变量数组中不需要是64位对齐的,实现者可以自由决定用合适的方法诸如用两个局部变量来存储该值。 jvm通过局部变量来传递方法引用中的参数。一个类方法引用中的任何参数通过连续的局部变量来传递(从局部变量0开始)。在一个实例方法引用中,局部变量0通常用来传递调用该方法对象实例的引用。其后任何参数通过从局部变量1开始的连续局部变量传递。   2.2 操作数栈(Operand Stacks) 每个帧都有一个后进先出(LIFO)栈,被称为操作数栈。一个帧操作数栈的最大深度在编译期决定,由与帧相关联方法的代码提供。 如果上下文明确,我们会把当前帧的操作数栈简称为操作数栈。 当帧创建时,其中的操作数栈是空的。通过jvm提供的指令加载常数、局部变量的值或域到操作数栈。操作数栈同时也用来准备参数传递给方法,接收方法的返回值。 例如,iadd指令将两个整数值相加。它要求相加的两个整数值是操作数栈顶的两个值(由以前的指令压入在那里)。两个整数值都从操作数栈弹出,他们相加,相加的和压回操作数栈。子计算可以嵌套在操作数栈上,其结果值可以被相邻的计算使用。 每个操作数栈项可保存任何jvm类型的值,包括long和double。 操作数栈的值必须用与他们的值类型相适当的方法来操作。例如,压入两个int值把它们当作long来处理或者压入两个float值随后通过iadd指令将他们相加,是不可能的。一小部分jvm指令(dup和swap)在运行数据区域操作作为原始值而不需要关心他们的类型,这些指令被定义不能用来修改或打断单独的值。class文件校验器加强了这些操作数栈操作的限制。 在任何时间点操作数栈有一个关联的深度,long和double类型深度为两个单位,其他类型深度为一个单位。   2.3 动态链接(Dynamic Linking) 每个帧都有一个当前方法类型的运行常数池的引用,用来支持方法代码的动态链接。方法的class文件代码与被调用的方法相关联,通过符号引用来访问变量。动态链接转译这些符号方法引用到具体的方法引用,在需要时加载class来解析未定义的符号,并且把变量访问转译成这些变量地址相关联的存储结构中合适的位移。 这种方法和变量的晚绑定在使用方法时引起的其他类中的改变而破坏代码的可能性很小。   2.4 正常的方法调用结束 方法调用在没有引起异常抛出时正常的结束(异常直接由jvm,或者执行显示throw语句抛出)。如果当前方法调用正常的结束,则一个值可以返回到正在调用该方法的方法。这在调用的方法执行return指令时发生,指令返回的选择必须与被返回值的类型(如果有)相合适。 如下情况帧用来恢复调用者的状态(包括局部变量和操作数栈),当调用者的程序计数器适当的增加跳过了方法调用指令。然后返回值(如果有)压入调用方法帧的操作数栈中,执行在这个帧中正常进行。   2.5 意外的方法调用结束 当方法中jvm指令执行引起jvm抛出异常,而且异常没有被方法处理,这个方法调用意外结束。执行throw语句显示的抛出异常,并且没有被当前方法捕获,会引起方法意外结束。意外结束的方法不会返回任何值给调用者。   图.1 运行期的数据区域     3 hello world 示例 下面是hello world代码执行时数据区域的情况。   图.2 执行println方法前   jvm栈中frame a为当前帧,该帧为类方法main的帧。 操作数栈顶部指向 Hello, world 字符串,接下来那个位指向 System.out 的对象。局部变量指向main方法中的args参数。程序计数器指向 main方法中调用的println 指令invokevirtual。   图.3 执行println方法时   当执行println方法时,frame a中操作数弹出操作数栈,创建新的帧frame b,并且该帧为当前活动帧,frame a为非活动。局部变量为两个参数的引用。程序计数器指向方法println。   图.4 执行完println方法   当执行完println方式时,frame b栈弹出终结,frame a重新成为当前活动帧,程序计数器从invokevirtual跳过指向return指令,操作数栈为空。heap中hello,world及out已经没有其他程序引用该对象,gc将会自动回收。

推荐:JVM调优系列:(二)JVM运行时数据区域

1) Method Area 2) Heap 3) Java Stacks 4) PC Registers 5) Native Method Stacks   JAVA的JVM的内存模型大致可分为3个区: 堆区: 1.存储的全部是对象,每个对

1 运行期的数据区域 jvm定义了各种运行期的数据区域,可以在执行程序时使用。有些数据区域在虚拟机启动时创建,当虚拟机中止时才被销毁。另外一些数据区域是单个线程的,单个线程的数据区域在线

相关阅读排行


用户评论

游客

相关内容推荐

最新文章

×

×

请激活账号

为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。

您的注册邮箱: 修改

重新发送激活邮件 进入我的邮箱

如果您没有收到激活邮件,请注意检查垃圾箱。