JVM内存模型

JVM内存布局

JVM内存模型_第1张图片

方法区

  • 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • 这区域的内存回收目标主要是针对常量池的回收和对类型的卸载
  • 和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。

本地方法栈

     而本地方法栈则为虚拟机使用到的native方法服务,可能底层调用的c或者c++,我们打开jdk安装目录可以看到也有很多用c编写的文件,可能就是native方法所调用的c代码。

  • 本地方法栈的功能和特点类似于虚拟机栈,均具有线程隔离的特点以及都能抛出StackOverflowError和OutOfMemoryError异常。
  • 不同的是,本地方法栈服务的对象是JVM执行的native方法,而虚拟机栈服务的是JVM执行的java方法。
  • HotSpot虚拟机不区分虚拟机栈和本地方法栈,两者是一块的。

JVM Stack(虚拟机栈)

栈是一个先进后出的数据结构。

JVM中的虚拟机栈是描述Java方法执行的内存区域,它是线程私有的。

栈中的数据都是以栈帧的格式存在,每个方法执行的同时都会创建一个栈帧,方法从开始调用到执行完成的过程,就是栈帧从入栈到出栈的过程。

在活动线程中,只有位于栈顶的帧才是有效的,称为当前栈帧。正在执行的方法称为当前方法,方法执行完会跳转到另一个栈帧上,栈帧是方法运行的基本结构。

栈帧中又包括局部变量表、操作栈、动态连接、方法返回地址等。

(1) 局部变量表:是存放方法参数和局部变量(基本数据类型与引用类型)的区域。相对于类属性变量的准备阶段和初始化阶段来说,局部变量没有准备阶段,必须显示初始化。

(2) 操作栈:是一个初始状态为空的桶式结构栈。在方法执行过程中,会有各种指令往栈中写入和提取信息。

(3) 动态连接:每个栈帧中包含一个在常量池中对当前方法的引用,目的是支持方法调用过程的动态连接。

(4) 方法返回地址:方法执行时有两种退出情况:第一,正常退出;第二,异常退出。无论何种退出情况,都将返回至方法当前被调用的位置,相当于弹出当前栈帧。

 

程序计数器

程序计数器:是一块较小的内存空间,可看作当前线程正在执行的字节码的行号指示器

为什么需要程序计数器

 JVM的多线程是通过线程轮流切换并分配CPU执行时间片的方式来实现的,任何一个时刻,一个CPU都只会执行一条线程中的指令。为了保证线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程间的程序计数器独立存储,互不影响

 

程序计数器作用

1 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理

2 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。

 

特点

一块较小的内存空间
线程私有。每条线程都有一个独立的程序计数器。
是唯一一个不会出现OOM的内存区域。
生命周期随着线程的创建而创建,随着线程的结束而死亡

 

Java堆:

  • Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。
  • 此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
  • Java堆是垃圾收集器管理的主要区域,称为"GC堆"
  • 如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

 

堆区是JVM中最大一块内存区域,存储着各类生成的对象、数组等,所有通过new创建的对象的内存都在堆中分配,JVM8中把运行时常量池、静态变量也移到堆区进行存储。堆区被细化可以分为年轻代、老年代,其实不分代完全可以,分代的唯一理由就是优化GC性能

如果没有分代,那我们所有的对象都在一块,GC的时候我们要找到哪些对象没用,这样就会对堆的所有区域进行扫描。而我们的很多对象都是朝生夕死的,如果分代的话,我们把新创建的对象放到某一地方,当GC的时候先把这块存“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。

在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。

  • 新生代。新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中

  • 旧生代。用于存放新生代中经过多次垃圾回收仍然存活的对象

  • 持久带(Permanent Space)实现方法区,主要存放所有已加载的类信息,方法信息,常量池等等

你可能感兴趣的:(JAVA)