JVM内存模型
废话不多说直接上干活
让我们直接来看一看JVM运行时的内存模型
程序计数器
程序计数器是虚拟机中比较小的一块区域,你们肯定有些疑惑?看上面的内存模型程序计数器应该很大的才对啊,我作为一个初学JVM的小白看了很多别人画的JVM也很疑惑程序计数器为何画这么大?
程序计数器可以说是当前线程执行的字节码指示器,字节码解释器的工作就是改变程序计数器来决定下一条要执行的字节码,所以它是程序控制流的指示器,分支、循环、跳转、异常处理等等都需要这个计数器。
程序计数器区域是一块线程隔离的区域,每一条线程都会有一个自己的程序计数器,互不干扰,独立存储。众所周知在单核CPU下,一个时间下只有一个线程在执行,并不是多个线程在同一时间共同运行,而是多个线程来回切换运的计数器行。程序计数器就是用来保证每一条线程经过CPU调度执行后能恢复到正确的位置。
虚拟机栈
它和程序计数器一样,是一块线程私有的区域,它随着一个线程的启动而创建,并随着线程的结束而释放。虚拟机栈描述的就是java方法执行的内存模型,每个方法被执行的时候,就会在虚拟机栈中创建一个栈帧,java方法执行和结束就是一个出栈和入栈的过程。java中方法的执行就是遵循着栈先进后出,后进先出的原则。
为了更方便理解我们来看一串代码
class Test{
public void a(){
// a 方法中 调用了 b 方法 b 进栈
b();
// b 方法执行完毕 出栈
}
public void b(){
}
}
public class SimulationStack {
public static void main(String[] args) {
Test test = new Test();
// 调用test的 a 方法 a进栈
test.a();
// a 方法执行完毕 出栈
}
}
1.当我们调用了 test类中的 a 方法时 a 进栈
2.a 方法在执行时 调用了 b 方法 b 进栈
3.b 方法执行完毕 b 出栈
4.a 方法执行完毕 a 出栈
栈帧中存放着局部变量表,方法索引,输如输出参数,Class File的引用、 父帧、子帧等数据。
在栈内存中规定了两种异常状况:
- 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverFlowError异常
- 如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存就会抛出OutOfMemoryError异常
本地方法栈
本地方法栈和虚拟机方法栈发挥的作用非常相似,只不过虚拟机栈是为虚拟机指向java方法服务,而本地虚拟机栈是为虚拟机使用到本地方法服务
与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常。
方法区
方法区和java堆一样,是所有线程共享的内存,所以字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在改区域,此区域属于共享区间。
静态变量,常量,类信息(构造方法,接口定义),运行时的常量池在方法区中,但是实例变量存在堆内存中,和方法区无关
如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。
运行时常量池
运行时常量池时方法区的一部分。Class文件除了有类的版本,字段,方法,接口等描述信息外,还有一项信息就是常量池表,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是说,并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中。
java堆
java堆是虚拟机所管理的最大的内存,是一块所有线程都共享的内存。在虚拟机启动时创建,此内存区唯一的目的就是存放对象的实例,GC垃圾回收的主要部分,百分之99的垃圾都由堆产出。
类加载器读取了类文件后,一般会把类,方法,常量,变量,放在堆中,堆保存我们所有引用类型的真实对象。
java堆内会划分为:新生区,养老区和永久区。
-
新生区
- 伊甸园区:存放new 出来的对象,当第一次垃圾回收没有被回收掉的对象,就会进入幸存区。
- 幸存区:幸存区中有幸存0区和幸存1区,这连个区是在堆中是动态交换的。对象会有一个交换上限,当在幸存区超过这个交换上限还没有被清理就会进去养老区。
-
养老区:进入养老区的对象,养老区的对象一般情况就不会被清理,如果养老区满了就会触发重量级的GC垃圾回收(重GC)。
-
永久区:存储一些基本类型数据,jvm内置数据。在JDK8以后,永久存储区有另外一个名字:元空间。
GC垃圾回收,主要是在伊甸园区和养老区。
Java堆既可以被实现成固定大小的,也可以是可扩展的,如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,也就是堆内存不足,Java虚拟机将会抛出OutOfMemoryError异常。
模拟一个OOM内存不足错误
public class SimulationOOM {
public static void main(String[] args) {
String str = "即使再小的帆也能远航";
while (true){ // 无限往新生区添加对象
str += str +
new Random().nextInt(999999999)+
new Random().nextInt(999999999);
}
}
}