Java虚拟机的主要任务是装载class文件并且执行其中的字节码
1、程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。如果执行的是native方法,则计数器为空。
每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,是线程私有的。
此内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError 情况的区域
2、Java 虚拟机栈
描述的是java方法执行的内存模型:每一个方法执行时都会创建一个栈帧来存储方法的变量表、操作数、栈动态链接方法、返回值、返回地址等信息。方法的调用对应着栈帧的入栈和出栈。栈的大小决定了方法调用的可达深度(递归的层次或嵌套调用多少层其他方法,-Xss参数可以设置虚拟机栈的大小)。栈的大小可以是固定的,也可以是动态扩展的。如果请求栈的深度大于最大可用深度,则抛出StackOverflowError;如果栈是可以动态扩展的,但没有内存空间支持扩展,则抛出 OutOfMemoryError 异常。
3.本地方法栈
与虚拟机发挥的作用是相似的,其区别在于Java虚拟机栈为执行java方法服务,而本地方法栈为执行 Native 方法服务。本地方法区也会抛出 StackOverflowError和OutOfMemoryError异常。
4.Java堆(Java Heap)
Java堆是虚拟机中所管理内存最大的一块。
Java堆是被所有线程共享的,在虚拟机启动时创建。
此区域主要是用来存放对象实例的。几乎所有的对象实例都存放在此区域。(是几乎,不是绝对,随着编译器的发展和逃逸技术的日趋成熟,对象实例也有可能被分配到栈里)
java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”。由于现在的回收算法基本都采用分代回收算法,所以可以将堆细分为:新生代和老年代,更细致一点有Eden区域,From Servivor 区域,To Servivor 区域。
Java堆可以处在物理上不连续的内存空间中,只要逻辑上连续即可。当前主流虚拟机都是可动态扩展的(通过-Xms和-Xmx来控制)。如果在堆中没有内存可以用来分配实例且无法动态扩展,则抛出OutOfMemoryError 异常。
5.方法区
方法区也是线程共享的。它用于存储被虚拟机加载的类信息,常量,静态变量,JIT编译器(即时编译器)编译后的代码等数据。方法区本质上是堆的一个逻辑部分。
6、运行时常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant PoolTable),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
补充:
在这里说一下关于String的内存问题:直接在双引号中创建和new的区别是什么呢?
对于字符串:其对象的引用都是存储在java栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。
通过双引号,可能创建一个对象或不创建对象实例。首先通过双引号是将”ABC”存放在常量池中的,如果常量池中没有”ABC”,则或创建一个对象,并将其引用存放在java栈中的局部变量表中。常量池的内容只会有一份,如果”ABC”已经存在,则无需创建新的对象。
通过new关键字创建String实例,会在堆中开辟一块空间,无论之前”ABC”这个字符串是否出现过。每一次new都会创建一个新的对象。
String a = “ABC”;
String b = new String(“ABC”);
String c =”AB “ + “C”
String d = new String(“ABC”);
有以下结论: a == b ->false
a == c –>true
b == d ->false
String e =”A”
String f = e+”BC”
此时: f == a->false
为什么呢?e是一个引用,在编译的时候不会确定,而a 和 e确实都是在编译是确定的。所以 f 会在堆中创建一个新的对象而不是直接指向常量池中的”ABC”。
String 有一个intern() 方法,native,用来检测在常量池是否已经有这个String存在。
总结:
1. java虚拟机栈和本地方法栈都是描述方法执行过程的内存模型。程序计数器,以及这两个栈都是线程私有的,他们的生命周期和所属线程一样。
2. 堆和方法区是线程共享的,在虚拟机启动时创建,关闭时销毁。