JVM 的内存划分
JVM 将内存主要划分为:方法区、栈、本地方法栈、堆、程序计数器。
方法区:又叫静态区,存放已被Java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等(static、final修饰的成员变量和字节码class);方法区中包含的都是在程序中永远的唯一的元素。
虚拟机栈:执行引擎每调用一个函数时,就为这个函数创建一个栈帧,并加入虚拟机栈。换个角度理解,每个函数从调用到执行结束,其实是对应一个栈帧的入栈和出栈。
本地方法区:本地方法栈与虚拟机栈所发挥的作用很相似,他们的区别在于虚拟机栈为执行Java代码方法服务,而本地方法栈是为Native方法服务。
堆:被所有线程共享的一块区域,在虚拟机启动时创建,所有的对象实例及数组都在堆上分配(使用new关键字,表示在堆中开辟一块新的存储空间)。
程序计数器:每个线程得有个计数器记录当前执行到那个指令。可以把它看成是当前线程所执行的字节码的行号指示器。
栈
栈帧
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素。 栈帧存储了方法的局部变量表、 操作数栈、 动态连接和方法返回地址等信息。 每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
(1)局部变量表
局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。
局部变量表的容量以变量槽(Variable Slot,下称Slot)为最小单位。
在方法执行时,虚拟机是使用局部变量表完成参数值到参数变量列表的传递过程的,如果执行的是实例方法(非static的方法),那局部变量表中第0位索引的Slot默认是用于传递方法所属对象实例的引用,在方法中可以通过关键字“this”来访问到这个隐含的参数。 其余参数则按照参数表顺序排列,占用从1开始的局部变量Slot,参数表分配完毕后,再根据方法体内部定义的变量顺序和作用域分配其余的Slot。
(2)操作数栈
操作数栈(Operand Stack)也常称为操作栈,它是一个后入先出(Last In FirstOut,LIFO)栈。 操作数栈的每一个元素可以是任意的Java数据类型,包括long和double。 32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。 当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作。 例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。
Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。
(3)动态链接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。 我们知道Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数。 这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这种转化称为静态解析。 另外一部分将在每一次运行期间转化为直接引用,这部分称为动态连接。
(4)方法返回地址
当一个方法开始执行后,只有两种方式可以退出这个方法。 第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者(调用当前方法的方法称为调用者),是否有返回值和返回值的类型将根据遇到何种方法返回指令来决定,这种退出方法的方式称为正常完成出口(Normal Method Invocation Completion)。
另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方法的方式称为异常完成出口(Abrupt Method Invocation Completion)。 一个方方法使用异常完成出口的方式退出,是不会给它的上层调用者产生任何返回值的。
堆的数组初始化 和 GC
代码
public static void main(String[] args){
int[] ages = new int[]{10,20,30};
ages = new int[]{40,50,60};
}
执行过程
int[] ages = new int[]{10,20,30}
创建过程序可分为三步:
1、声明int类型的数组ages —— int [] ages;
2、在堆中初始化数组 —— new int[]{10,20,30};
3、把被初始数组的地址赋值给ages变量
ages = new int[]{40,50,60}
1、在堆中初始化了一个新的数组
2、把数组的地址重新赋值给了ages
若堆中的内存空间没有被引用的时候,就会变成垃圾,等着垃圾回收器回收。
GC 垃圾回收机制
Java是自动垃圾回收机制。程序员不需要再动手控制内存的释放。当JVM发现内存资源紧张的时候,会自动的清理无用的对象。
JVM的垃圾回收机制中,判断一个对象是否死亡,并不是根据是否还有对象对其有引用,而是通过可达性分析。对象之间的引用可以抽象成树形结构,通过树根(GC Roots)作为起点,从这些树根往下搜索,搜索走过的链称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明这个对象是不可用的,该对象会被判定为可回收的对象。
、
对象堆的内存分析
代码
// 创建对象: 类 对象名称 = new 类名();
Person zly = new Person();
// 赋值:描述状态具体的值
zly.name = "赵丽颖";
zly.age = 20;
zly.height = 165;
Person rh = new Person();
rh.name = "如花";
rh.age = 18;
rh.height = 180;
zly = ru;
System.out.println(zly.name);
执行过程
Person zly = new Person();
zly.name = "赵丽颖";
zly.age = 20;
zly.height = 165;
Person rh = new Person();
rh.name = "如花";
rh.age = 18;
rh.height = 180;
zly = ru;
System.out.println(zly.name);
// 输出 : 如花
zly = null;
System.out.println(zly.name);
会报 空指针异常(java.lang.NullPointerException)
基本数据类型和引用数据类型
声明一个基本数据类型的变量(在栈中),必须初始化,否则无法使用。
声明一个引用类型的变量(在堆中),都有一个默认的初始值。
基本数据类型
只有一块存储空间,在栈中,存放的是具体的值。
引用数据类型
有两块存储空间,一个在栈中,一个在堆中,栈中存放的是堆中的地址。
5种引用数据类型:类、接口、数组、枚举、注解
使用new关键字创建出来的类型都是引用数据类型。
public static void main(String[] args) {
int a = 10;
Person zly = new Person();
zly.name = "赵丽颖";
zly.age = 20;
zly.height = 165;
}
基本数据类型——值传递
//基本数据类型它是属于值传递(传递 是一个具体值)
static void change(int x) {
System.out.println("chang方法一进来 x = "+ x);
x = 100;
System.out.println("chang方法最后 x = "+ x);
}
public static void main(String[] args) {
int x = 20;
change(x);
System.out.println("main方法最后 = "+ x);
}
执行步骤
1、main 方法栈帧入栈
2、change 方法栈帧入栈
3、change 方法栈帧出栈
执行结果
chang方法一进来 x = 20
chang方法最后 x = 100
main方法最后 = 20
引用数据类型——地址传递
// 功能:交换一个数组的第一个元素和第二个元素
static void swap(int[] nums) {
System.out.println("num=" + nums);
// 1. 把数组的第0个位置取出来,放到一个临时的变量当中 10
int temp = nums[0];
// 2. 把第1个位置的值出来,赋值给第0个位置
nums[0] = nums[1];
// 3. 把临时变量的值赋值给第1个位置
nums[1] = temp;
System.out.println("num={" + nums[0] + "," + nums[1] + "}");
}
public static void main(String[] args) {
int[] nums = new int[] { 10, 20 };
System.out.println("num={" + nums[0] + "," + nums[1] + "}");
swap(nums);
System.out.println("num={" + nums[0] + "," + nums[1] + "}");
}
执行步骤
main方法中:
int[] nums = new int[] { 10, 20 };
System.out.println("num={" + nums[0] + "," + nums[1] + "}");
swap方法中:
System.out.println("num=" + nums);
// 1. 把数组的第0个位置取出来,放到一个临时的变量当中 10
int temp = nums[0];
swap方法中:
// 2. 把第1个位置的值出来,赋值给第0个位置
nums[0] = nums[1];
// 3. 把临时变量的值赋值给第1个位置
nums[1] = temp;
System.out.println("num={" + nums[0] + "," + nums[1] + "}");
main方法中:
System.out.println("num={" + nums[0] + "," + nums[1] + "}");
执行结果
num={10,20}
num=[I@70dea4e
num={20,10}
num={20,10}