Java虚拟机(一)——内存区域理解

说明:本文内容主要参考了《深入理解Java虚拟机》第2版。

一,概述

最近因为辞职了,玩了一段时间了,有时间去学习。加上之前买了一二本书,有不少却没有仔细去看,今天状态还不错,刚好看到JVM相关的内容,觉得还是在博客里好好总结一下。

本文主要还是对JVM的内存区域进行较为详细的说明,暂时不结合实现开发过程中遇到的内存溢出等问题进行说明。

二,Java运行时数据区域

Java虚拟机所管理的内存包含以下几个运行时数据区域。如下图:

下面对每个区域进行说明。其中,方法区和堆是纯种共享的,程序计数器、本地方法区、虚拟机栈是线程私有的。

2.1,程序计数器(Program Counter Register)

是一块较小的内存区域,可看作是当线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

如果线程正在执行一Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的Native方法,这个计数器值则为(Undefined)。此内在区域是唯一一个在JVM规范中没有规定任何OutOfMemoryError情况的区域。

2.2,虚拟机栈(VM Stack)

虚拟机栈是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一从此栈帧(Stack Frame,是方法运行时的基础数据结构)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

经常有人把Java内存分为堆内存(Heap)和栈内存(Stack),这种方法比较粗糙,Java内存区域的划分实际上远比这个复杂。这种划分方式的流行只能说明大多数程序员最关注的、与对象内存分配关系最密切的内存区域是这两块。其中所说的“栈”就是虚拟机栈,或者说是虚拟机栈中局部变量部分。

局部变量表存储了编译期可知的各种基本数据类型(char、boolean、byte、short、int、long、float、double)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。

2.3,本地方法栈(Native Method Stack)

本地方法栈与虚拟机栈所发挥的作用是非常相似的,区别是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

2.4,堆(Heap)

堆是虚拟机所管理的内存中最大的一块。是被线程所共享的,在虚拟机启动时创建。堆内存区域的唯一目的就是存放对象实例,几乎所有对象实例都在这里分配内存。但是随着JIT编译器的发展和逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不那么“绝对”了。

堆是垃圾收集器管理的主要区域,因此很多时候也被称”GC堆“。

2.5,方法区(Method Area)

方法区是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然JVM规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫Non-Heap(非堆),目的应该是与Java堆区分开来。

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。运行时常量池相对于Class文件常量池有一个重要特性是具有动态性,Java语言并不要求常量一定只有编译期间才能产生,也就是并非预置入Class文件中常量池内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的就是String类的intern()方法。

三,总结

通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用JVM中的栈空间;而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为Eden、Survivor(又可分为From Survivor和To Survivor)、Tenured;方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的100、"hello"和常量都是放在常量池中,常量池是方法区的一部分,。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量池空间不足则会引发OutOfMemoryError。

String str = new String("hello");

上面的语句中变量str放在栈上,用new创建出来的字符串对象放在堆上,而"hello"这个字面量是放在方法区的。

猜你喜欢

转载自blog.csdn.net/qinxian20120/article/details/81238037