上一讲带着大家踏入了Java虚拟机的大门,从这一讲开始,进入专题的第一个版块——Java虚拟机的自动内存管理机制。
说起内存,大家很容易就想到了内存溢出,的确,对于Java工程师来说,谁的一生不会经历OutOfMemory呢,要么是Heap Space家起火,要么是Stack家淹水了,要么就是PermGen被打劫了。在学习如何定位这些异常发生的原因并提出解决方案之前,我们必须了解一下,Java虚拟机是如何划分自己的内存区域的。
本文是Effective Java专栏Java虚拟机专题的第二讲,如果你觉得看完之后对你有所帮助,欢迎订阅本专栏,也欢迎您将本专栏分享给你身边的工程师同学。
在学习本节课程之前,建议您先了解一下以下知识点:
一个Java进程启动后,会被划分一块类似于疆土的内存区域,虚拟机将这块大的内存,按照所存储的数据类型,划分为不同的区域进行管理,Java虚拟机的运行时数据区,可以用下面这张图来表示:
下面就来对这张内存区域模型图作详细的讲解。
程序计数器在虚拟机内存中是一块很小的区域,这个计数器记录了当前线程执行到了哪一行的字节码指令。每条线程都拥有一个独立的程序计数器,有了这个计数器,才能在不停的线程切换中,让线程记得下一条要执行的指令。
程序计数器是唯一一个在Java虚拟机规范中没有规定任何OutOfMemory异常的区域。
和程序计数器一样,Java虚拟机栈也是线程私有的。虚拟机栈主要存储的内容是方法执行时的局部变量表,包括各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(指向一个对象地址的指针)和returnAddress类型(指向一条字节码指令的地址)。
其中,64位长度的long和double会占用2个局部变量空间,其余数据类型只占用1个。局部变量表所需的内存空间在编译器就分配完成,也就是说,进入一个方法时,这个方法需要分配多大的局部变量空间是完全确定的。
可能这样讲大家还有点模糊,这里上个图:
每个线程都拥有一个虚拟机栈,线程中每个方法执行时都会在栈中创建一个栈帧,栈帧中就包含了上面所说的数据,方法每递归一次,就新建一个栈帧,依此类推。后面讲到虚拟机字节码执行引擎时,会对这张图作更详细的介绍。
在Java虚拟机规范中,这个区域有两种异常情况:
本地方法栈发挥的作用和虚拟机栈是十分相似的,主要都是存储着方法执行的局部变量的信息,不同的是虚拟机栈是为执行Java方法(也就是字节码)服务,而本地方法栈是为虚拟机使用到的本地Native方法服务。
基于两者的相似性,HotSpot虚拟机直接将两者合二为一。
和虚拟机栈一样,本地方法栈也有StackOverflow和OutOfMemory异常。
Java堆是Java虚拟机内存中最大的一块,是被所有线程共享的一块内存区域。Java堆负责存放Java对象实例,因此Java堆也是垃圾收集的主要区域。
Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。
可以通过-Xmx和-Xms参数来控制堆的大小,当堆没有内存完成实例分配,且无法继续扩展时,就会抛出OutOfMemory异常。
前面提到了虚拟机栈是用来管理Java方法执行的内存信息的,因此,很明显,这里的方法区存储的并不是方法执行的信息。
方法区用于存储已被虚拟机加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据,也是各个线程共享的内存区域。
直接内存并不是虚拟机运行时数据区的一部分,但是Java可以对这部分的内存进行使用,比如JDK 1.4新加入的NIO,它可以通过Native函数库直接在机器内存中获取内存,然后通过存储在Java堆中的对象,作为这块内存的引用进行操作,避免了在Java堆和Native之间来回复制数据,提高了操作的性能。
直接内存的大小只受到本机总内存的限制,因此,如果直接内存使用过多,超过了机器的物理内存限制,就会导致OutOfMemory异常。
这一讲,主要给大家介绍了Java虚拟机是如何划分内存以及各个内存区域的功能和职责。了解了这些之后,我们就能够理解为什么会发生各种内存异常的异常了,在下一讲,我将在自己的机器上,演示各种内存异常是如何发生的。
如果要你在自己的机器上模拟Java堆的内存溢出,你会怎么做?欢迎在评论区写下你的想法,O(∩_∩)O谢谢。
上一讲的问题是——“我们经常看到有人说他掌握J2SE、J2ME、J2EE,也看过有人说他很懂Java SE、Java ME、Java EE,那么到底应该叫是J2XX还是Java XX呢?”
这个问题涉及到Java发展的历史,1998年12月4日,JDK1.2发布,Sun将Java技术体系拆分为3个方向——J2SE、J2ME、J2EE;而到了2006年12月11日,JDK1.6发布,Sun终结了J2XX的命名方式,启用Java SE、Java ME、Java EE的命名方式。因此,在面试或者平时写作的时候,建议大家采用Java XX的方式吧,就像Java的首字母要大写一样。