当JVM运行起来的时候就会给内存划分空间,那么这块空间称之为运行时数据区。
1 | # 当一个Java源程序编译成class字节码文件之后,字节码文件里存放的都是二进制的汇编命令, |
运行时数据区将划分为以下几块内容:
栈
每一个线程运行起来的时候就会对应一个栈(线程栈),栈当中存放的数据是被当前线程所独有的。而栈当中存放的是栈帧,当线程调用一个方法的时候,就会形成一个栈帧,并将这个栈帧进行压栈操作,当方法执行完之后就会将这个栈帧进行出栈操作。这个栈帧里面包括(局部变量、操作数栈、指向当前方法对应类的常量池引用、方法的返回地址等信息)。
1 | # 由于局部变量都是存放在栈中,而每一个线程都对应自己的线程栈,因此局部变量是线程安全 |
本地方法栈
- 本地方法栈的机制和栈的机制类似,区别在于,栈是运行Java所实现的方法,而本地方法栈是运行的本地方法(Native Method)。所谓的本地方法指的是在本地jvm中需要调用非Java语言所实现的方法,例如c语言。在JVM的规范中,其实没有强制性要求实现方一定要划分出本地方法栈的和具体的实现,这一部分可以根据实现方具体要求来实现。因此在HotSport虚拟机的实现中就将方法栈和本地方法栈二合为一。
程序计数器
- 程序计数器也可以称之为PC寄存器。它主要用于存放当前程序下一条将要执行的指令地址。CPU会根据这个地址找到对应的指令来执行。通俗的讲就是指令缓存。这个寄存器是有JVM内部实现的,并不是物理概念上的寄存器,但是JVM在实现功能的逻辑上是相同的。
堆
堆内存中主要存放创建的对象以及数组。 堆内存是可以被多个线程所共享的一块区域,因此多个线程栈都可以去访问同一块堆的内存区域。堆里面的每一对象都存放了该实例的实例变量。
当在方法中定义了一个局部变量,如果这个变量是基本数据类型,那么这个变量的值就直接存放在栈中,如果这个变量是引用数据类型,那么这个对象变量就存放在堆内存中,而栈中存放的是一个指向堆内存中这个对象的首地址。
1 | # Java中除了8个基本数据类型以外的所有类型都是引用数据类型 |
- 引用
- 更改
- 数组
- 循环
实例变量和静态变量的区别:
- 实例变量: 实例变量是随着对象的创建而创建,而实例是存放在堆中,所以实例变量自然也就跟实例一并保存在堆内存。只要创建多少个实例,就会有多少份实例变量。当实例被回收的时候,实例变量也随之而销毁。
- 静态变量: 静态变量也叫类变量,它是在类加载的时候就已经初始化好,并存放在方法区,并且只有一份,所以它是被多个实例所共享的一个变量。
方法区
方法区在JVM中也是一个非常重要的一块内存区域,它和堆一样,是可以被多个线程所共享的一块区域。这个区域中主要存放了每一个加载的class文件信息。
在一个class文件中主要包含
魔数
(代码中出现但没有解释的数字常量或字符串)(用来确定是否是一个class文件)、常量池(常量池在下面会有完整说明)、访问标志(当前的class是类还是接口,是否是抽象类,是否是public修饰,是否使用了final
修饰等描述信息…)、字段表集合信息(使用什么访问修饰符、是实例变量还是静态变量,是否用final
修饰等描述信息…)、方法表集合信息(访问修饰符,是否静态方法,是否用final
修饰,是否用了synchronized
修饰,是否是native
方法…)等内容。当一个类加载器加载一个class文件的时候,会根据这个class文件的内容创建一个Class对象,而这个Class对象就包括了上述的这些内容。后续要创建这个类的所有实例,都是通过这个Class对象创建出来的。
常量池
常量池也是方法区中的一部分,它存放的内容是class文件中最重要的资源,JVM为每一个class对象都维护一个常量池。它主要存储两种类型的常量。
字面常量:通常就是在Java中定义的字面量值,如:int i =1,这个1就是字面量;String s = “abc”,这个abc就是字面量。或者使用final修饰的常量值等等。
- 符号引用:主要包括类和接口的完整类名、属性字段的名称和描述符、方法名称和描述符等信息
- 在Java当中,8个基本数据类型都有对应的包装类型,而大部分包装类型都实现了常量池的技术,除了Double 和 Float 类。
1 | # 在JDK8之后,方法区已经取消,方法区被一个叫MetaSpace,它和堆合并到一起管理 |
内存运行时数据区