一、JVM内存的构
Java虚拟机会将内存分为几个不同的管理区,这些区域各自有各自的用途,根据不同的特点,承担不同的任务以及在垃圾回收时运用不同的算法。总体分为下面几个部分:
程序计数器(Program Counter Register)、JVM虚拟机栈(JVM Stacks)、本地方法栈(Native Method Stacks)、堆(Heap)、方法区(Method Area)
如下图:
1、程序计数器(Program Counter Register)
这 是一块比较小的内存,不在Ram上,而是直接划分在CPU上的,程序员无法直接操作它,它的作用是:JVM在解释字节码文件(.class)时,存储当前线程所执行的字节码的行号,只是一种概念模型,各种JVM所采用的方式不同,字节码解释器工作时,就是通过改变程序计数器的值来选取下一条要执行的指令, 分支、循环、跳转、等基础功能都是依赖此技术区完成的。还有一种情况,就是我们常说的Java多线程方面的,多线程就是通过线程轮流切换而达到的,同一时刻,一个内核只能执行一个指令,所以,对于每一个程序来说,必须有一个计数器来记录程序的执行进度,这样,当线程恢复执行的时候,才能从正确的地方开始, 所以,每个线程都必须有一个独立的程序计数器,这类计数器为线程私有的内存。如果一个线程正在执行一个Java方法,则计数器记录的是字节码的指令的地址,如果执行的一个Native方法,则计数器的记录为空,此内存区是唯一一个在Java规范中没有任何OutOfMemoryError情况的区域。
2、JVM虚拟机栈(JVM Stacks)
JVM虚拟机栈就是我们常说的堆栈的栈(我们常常把内存粗略分为堆和栈),和程序计数器一样,也是线程私有的,生命周期和线程一样,每个方法被执行的时候会产生一个栈帧, 用于存储局部变量表、动态链接、操作数、方法出口等信息。方法的执行过程就是栈帧在JVM中出栈和入栈的过程。局部变量表中存放的是各种基本数据类型,如 boolean、byte、char、等8种,及引用类型(存放的是指向各个对象的内存地址),因此,它有一个特点:内存空间可以在编译期间就确定,运行期不在改变。这个内存区域会有两种可能的Java异常:StackOverFlowError和OutOfMemoryError。
3、本地方法栈(Native Method Stacks)
从名字即可看出,本地方法栈就是用来处理Java中的本地方法的,Java类的祖先类Object中有众多Native方法,如hashCode()、 wait()等,他们的执行很多时候是借助于操作系统,但是JVM需要对他们做一些规范,来处理他们的执行过程。此区域,可以有不同的实现方法,像我们常用的Sun的JVM就是本地方法栈和JVM虚拟机栈是同一个。
4、堆(Heap)
堆内存是内存中最重要的一块,也是最有必要进行深究的一部分。因为Java性能的优化,主要就是针对这部分内存的。所有的对象实例及数组都是在堆上面分配的(随着JIT技术的逐渐成熟,这句话似乎有些绝对,不过至少目前还基本是这样的),可通过-Xmx和-Xms来控制堆的大小。JIT 技术的发展产生了新的技术,如栈上分配和标量替换,也许在不久的几年里,即时编译会诞生及成熟,那个时候,“所有的对象实例及数组都是在堆上面分配的”这句话就应该稍微改改了。堆内存是垃圾回收的主要区域,所以在下文垃圾回收板块会重点介绍,此处只做概念方面的解释。在32位系统上最大为2G,64位系统上无限制。可通过-Xms和-Xmx控制,-Xms为JVM启动时申请的最小Heap内存,-Xmx为JVM可申请的最大Heap内存。
5、方法区(Method Area)
方法区是所有线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量等数据,一般来说,方法区属于持久代(关于持久代,会在GC部分详细介绍,除了持久代,还有新生代和旧生代),也难怪Java规范将方法区描述为堆的一个逻辑部分,但是它不是堆。方法区的垃圾回收比较棘手,就算是Sun的 HotSpot VM在这方面也没有做得多么完美。此处引入方法区中一个重要的概念:运行时常量池。主要用于存放在编译过程中产生的字面量(字面量简单理解就是常量)和引用。一般情况,常量的内存分配在编译期间就能确定,但不一定全是,有一些可能就是运行时也可将常量放入常量池中,如String类中有个Native方法 intern()。