要了解Java在运行时的内存分配就要清楚JVM运行时数据区,如图所示:
1 程序计数寄存器(Program Counter Register)
是线程私有的一块较小的内存,用来记录当前线程所执行的字节码的行号,以便线程切换后能够恢复到正确的执行位置。如果线程执行的是本地(Native)方法,则程序计数器的值为空(undefine)。这块内存是JVM运行规范中唯一没有规定任何内存溢出(OutOfMemoryError)的区域。
2 虚拟机栈(VM Stack)
每个线程都有一个私有的虚拟机栈,它描述的是Java方法执行的内存模型,即每个方法在执行的时候都会创建一个栈帧(Stack Frame),栈帧中存储了:
1)局部变量表。该表中存放了编译期就可知的:各种基本数据类型(8种:byte、short、 int、 long、 float、 double、char、 boolean )、对象引用、returnAddress类型(指向一条字节码指令地址)。局部变量表所需的内存大小在编译期就完成了内存分配,也就是说当进入一个方法时,该方法需要在栈帧中创建的局部表空间内存就固定了,运行期不会改变。
2)操作数栈。
3)动态链接。
4)方法出口等。
方法从被调用到执行完毕对应了一个栈帧在虚拟机中的入栈和出栈过程。
针对虚拟机栈有两种异常(Error):
1)如果线程请求的栈深度大于JVM所允许的深度,就会抛出栈溢出异常(StackOverflowError)。
2)如果栈扩展时无法申请到足够的内存,就会抛出内存溢出异常(OutOfMemoryError)。
3 本地方法栈(Native Method Stack)
本地方法栈只为本地方法服务,JVM规范没有强制规定本地方法栈中的方法使用的语言、使用方式、数据结构,所以因JVM不同,实现不同。HotSpot虚拟机就将虚拟机栈和本地方法栈合二为一了。所以针对本地方法栈的异常同虚拟机栈。
4 Java 堆(Heap)
Java堆是虚拟机管理的内存中最大的一块,被所有线程共享,在虚拟机启动时创建,通过参数“-Xmx”,“-Xms”控制大小。
所有对象实例和数组都要在堆上存放。
Java堆是垃圾回收器管理的主要区域。
为了更好的回收、更好的分配内存,从不同的角度对Java堆进行了进一步的细分:
1)从内存分配角度看,线程共享的Java堆中可能分配出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。
2)从内存回收角度看,GC一般采用分代回收算法,所以Java堆可以分为年轻代和年老代。
针对Java堆的异常:如果堆中内存不够继续进行实例内存分配,且堆无法再次扩展时,就会抛出OutOfMemoeyError。
5 方法区(Method Area)
和Java堆一样,被所有线程共享。用于存储已被虚拟机加载的:
1)类信息(class metadata)。2)常量(Ingeter ,String等)。3)静态变量(class static vriables)。4)即使编译器编译的代码等。
虽然JVM规范把方法区描述为堆的一个逻辑部分(真实不存在),但是他有个别名Non-heap。
原来有人把方法区成为“永久代”,值得一提的是在Java8中,根据JEP122,永久代PermanentGeneration已经被从HotSpot中removed,这是因为JRockit和HotSpot合并了。
针对方法区的垃圾回收,收集的是:类信息的卸载和常量池的回收。
针对方法区的异常:当方法区无法满足内存分配时,会抛出内存溢出异常(OutOfMemoryError)。
6 运行时常量池(Run-Time Constant pool)
运行时常量池其实是方法区的一部分。类文件中有一部分是常量池表(constant_pool table),用于存放编译器生成的“字面量”和“符号引用”,这部分内容将在类加载后存到方法区的运行时常量池中。也就是说,每一个类(class)都会根据常量池表(constant_pool table)来1:1创建一个此类(class)对应的运行时常量池。
特别的:Sting str="abc";在编译期就确定了,str被存入常量池,而String str=new String("abc");此时不是常量,是对象,所以不能再编译器确定,不能存入常量池中,他们有自己的地址空间。
再补充介绍一点:存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用;
既然运行时常量池是方法区的一部分,自然受到方法区限制,当运行时常量池无法再申请到内存时,将抛出OutOfMemoryError异常。
7 备注
1)字面量。就是给变量赋的值,只能出现在“=”的右侧。比如:
<span style="font-size:14px;">int a=6;//a为变量,6为字面量</span>
2)符号引用。符号引用是一个字符串,它给出了被引用的内容的名字并且可能会包含一些其他关于这个被引用 项的信息——这些信息必须足以唯一的识别一个类、字段、方法。这样,对于其他类的符号引用必须给出 类的全名。对于其他类的字段,必须给出类名、字段名以及字段描述符。对于其他类的方法的引用必须给出 类名、方法名以及方法的描述符。
3)即时编译器。当JVM发现某个方法或代码块运行特别频繁时,就会把这些代码认定为“热点代码(Hot Spot Code)”,然后JVM会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为:即时编译器(Just In Time Compiler,JIT)。
衷心感谢“有且仅有的路”博主的分享,受益良多。