听着这个名字感觉十分高大上,其实这是程序员们很熟悉的“栈”!我们经常说,Java内存分为“堆”和“栈”,这只是一种粗略的分法,因为我们比较关注这两部分区域,其中的“栈”就是Java虚拟机栈,或者说具体一点就是局部变量表。
局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
对于大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块。我们常说的“堆”内存也是指这一块。Java堆是所有线程共享的一块的内存,因此当编写多线程程序时,一定要注意线程的安全,主要关注这块的内存的读写同步与互斥。
我们都知道,几乎所有的对象都会分配在堆内存上,因此Java堆是垃圾回收管理的主要区域。从内存回收的角度,由于现在垃圾回收基本都采用分代收集算法,所以Java堆还可以再细分为:新生代和老年代;再细致一点:Eden区、From Survivor区、To Survivor区等。
方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。这个区域经常被程序员称为“永久代”,这是因为Sun hotspot设计团队把垃圾回收扩展到了方法区,这部分区域如同Java堆一样被垃圾回收器管理。但是其他虚拟机,如IBM J9没有这个概念。
大多数程序员遇到过这样的错误:PermGen OutofMemorryError,特别是在用Eclipse等编译器热部署Web程序时,当程序中用到了许多类似Spring这样预加载许多类信息、方法信息的框架时,经常出现这样错误,主要原因就是方法区的内存不够用了!Oracle Java8发布后,将这个区域移动到了本地内存,取名为Metaspace,理论上,不会再出现PermGen OutOfMemorryError,因为本地内存对于普通程序来说,绝对够大了。
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
Java虚拟机对Class文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。但对于运行时常量池,Java虚拟机规范没有做任何细节的要求,不同的提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。不过,一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。
运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只能在编译期产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。
既然运行时常量池是方法区的一部分,自然会受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。