JVM-运行时数据区域

200 阅读6分钟

JVM-1-运行时数据区域

1.运行时数据区域

经过编译产生的.class文件交由类加载子系统加载后并交给执行引擎执行,执行的时候对产生的数据进行保存的区域叫做运行时数据区域。

2.程序计数器

程序计数器记录着当前线程正在执行字节码指令的地址(仅限java方法),如果是本地方法,那么程序计数器的计数值为空。由于多线程之间不停的切换,不同线程的执行状态都要被记录下来,所以程序计数器是线程私有的,而且它也是运行时数据区域唯一一个不会发生内存溢出异常的区域。

3.虚拟机栈

虚拟机栈是java方法执行的数据结构。每个方法的执行都会在虚拟机栈中创建一个栈帧,每个栈帧包括局部变量表,操作数栈,动态链接,方法返回地址等内容。当方法执行完毕以后,该栈帧从虚拟机栈中弹出,然后程序计数器指向下一条指令。

  • 局部变量表:局部变量表存储着局部变量方法参数,局部变量表被一个个变量槽slot划分,如果该方法是实例方法,局部变量表的0索引处是指向该方法所属对象的引用。为了节省空间局部变量表中的内存可以复用,当程序计数器超过了某个slot的作用域,那么这一处的slot便可以被其他的变量使用(注意,得等到其他变量使用这块slot,之前那块slot指向的对象才会被回收)。

    注意:引用的存储位置到底在哪里?

    class Demo01{
        //成员变量存在堆中
        Person person=new Person();
       public void fun(){
           //局部变量存在栈中
           Person person1=new Person();
       }
    }
    

    方法中的局部变量是存储在虚拟机栈中的局部变量表里的,而实例变量则存储在堆中!

    记住!虚拟机栈,是执行方法的数据结构!

  • 操作数栈:操作数栈也是栈结构,一开始操作数栈是空的,随着方法执行,会有各种字节码指令对操作数栈进行写入或者提取操作,也就是出栈/入栈操作。在大多数虚拟机的实现里会将两个栈桢进行重叠,让一个栈桢的操作数栈与另一个栈桢的局部变量表重叠,这样可以共享一部分数据。

  • 动态链接:指当前方法在常量池中的符号引用,目的是支持在运行期间符号引用转为直接引用。

  • 方法返回地址:当方法执行后只有两种方式退出,一种是当执行引擎收到了方法返回的字节码指令,如果有结果,将其结果返回给方法的调用者。还有一种就是执行过程中出现了异常。

    1. 无返回结果且正常退出,则程序计数器移到下一个字节码指令
    2. 发生异常,将异常抛给能够解决的栈帧
    3. 有返回结果,则将返回值压入上层调用的栈帧
4.本地方法栈

虚拟机栈是为虚拟机执行的java方法所服务的,本地方法栈是为Native方法服务的,不受JVM制约。

5.堆

java堆是用于存储java实例对象和数组,被所有线程共享的一块内存区域。堆是垃圾收集管理的主要区域,所以堆也叫做GC堆。堆在内存分配上可以物理不连续,逻辑连续,当创建实例对象分配内存不足时会出OOM错误。

6.方法区

方法区与堆一样,是一个线程共享内存的区域,它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译的代码等,他被描述为堆的一个逻辑部分,但它却被区分开叫做‘非堆’。方法区中分配的内存会受到垃圾回收或其他与分配给堆的正常数据结构相关的行为的影响。

  • JDK1.8版本之前的方法区由永久代来实现,这样做的缺点是容易发生OOM异常,因为字符串常量池存储于永久代中,无法确定永久代的大小,如果永久代过大,那么老年代就容易OOM,如果过小永久代就容易OOM。
  • JDK1.8版本之后方法区由元数据空间实现,移除了永久代,除字符串常量池被移动到堆中,其余数据全部移动到元数据空间里,这样减少了垃圾回收的复杂度。
6.1.运行时常量池

class文件里包含了很多信息,由魔数,副版本号,主版本号,常量池计数器,常量池数据区,访问标志,类索引,父类索引,接口计数器,接口数据区等。

在方法区中除了类的方法以及接口描述等class文件描述信息,还有一部分信息是存储在方法区中的class常量池中,class常量池存储常量以及符号引用,在类被加载时会将class常量池中的信息存储于运行时常量池中。运行时常量池的一个特点就是它并不要求所有常量都必须在编译时期产生,运行时期也可以将常量添加进运行时常量池中(个人觉得这句话是针对jdk1.7之前的),如String类的intern()方法。

6.1.字符串常量池

字符串常量池在1.7前是在方法区里面的,1.7之后就被放入堆中,字符串常量池的数据结构是map结构,它存储的其实并不是字符串常量,而是驻留字符串的引用,当类被加载的时候,class常量池中的字符串常量不会进入运行时常量池中,而是创建对象在中,如果是"string str="xxx""这种形式那么会在字符串常量池中为堆中的"xxx"这个对象创建一个引用并指向该驻留字符串,下次再有"string str1="xxx""的话它会先去查询字符串常量池,如果有该字符串的引用,那么直接返回该引用。如果是"string str=new string("xxx")"的话不管字符串常量池有没有该"xxx"字符串的引用都会在堆中创建一个对象。如果是"String str=new String("xxx"+new String("qwe"))"这种形式,里面的字符串是无法在编译时期确定的,所以"xxxqwe"是不会进入常量池的只有"xxx","qwe"进入常量池,如果是"String str="qwe"+"asd""这种形式,"qwe"和"asd"是不会进入常量池的,只有"qweasd"进入常量池。

直接内存

直接内存并不是运行时数据区域的一部分,但是这部分也会被频繁地使用。主要用于NIO类。(应用场景:RocketMQ中消息持久化)

(本文仅用于记录平时学习心得,如有误导,恳请指教)