先康康解栈/堆/方法区
1.栈
栈(Stack)是一块线程私有的空间, 每个方法分配一个栈帧,栈帧之间不共享 ,作用存放局部变量(基本数据类型和对象的引用 )。
一个栈一般由三部分组成:局部变量表、操作数据栈和帧数据区。
1)局部变量表:我们平时所说的栈其实就是栈里面的局部变量表,这里存放基本数据类型变量和对象的引用(所需内存在编译时期完成分配,方法运行时期不改变局部变量表大小,四个字节占用一个局部变量空间)。
2)操作数栈:主要保存计算过程的中间结果,同时作为计算过程中的变量临时的存储空间,例如,替换两个值时引入的temp。
3)帧数据区:除了局部变量表和操作数据栈以外,栈还需要一些数据来支持常量池的解析,这里帧数据区保存着。
2.Java堆
Java堆用于存放new创建的对象和数组,Java堆是被所有线程共享的一块区域,在虚拟机启动时创建 。根据垃圾回收机制的不同,Java堆有可能拥有不同的结构,最为常见的就是将整个Java堆分为新生代和老年代。其中新生代存放新生的对象或者年龄不大的对象,老年代则存放老年对象。
3.方法区
方法区与堆一样是线程共享的一块内存区域。
- 用于储存已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码(class文件)等数据。
- 垃圾收集行为在方法区很少出现,这块区域回收的主要目标是针对常量池的回收和对类型的卸载 。
- 方法区的大小决定系统可以保存多少个类。如果系统定义太多的类,导致方法区溢出。虚拟机同样会抛出内存溢出的错误。
- 运行时常量池:
1)常量池用于存放编译期生成的各种字面量和符号引用(还有翻译出来的直接引用),这部分内容在类加载后进入方法区的运行时常量池中存放。
2)运行时常量池相对于Class文件常量池的另一个重要特征是具备动态性,运行期间也可能将新的常量放入池中。
//字面量:如文本字符串,声明为final的常量值等。
public static final int a = 777;
String name ="Uzi";
/* 符号引用:
类和接口的全限定名
字段的名称和描述符
方法的名称和描述符*/
new一个对象
啥?没有对象?我也没有对象,那没事了!现在开始找一个对象,啊不对,扯远了。是创建一个对象,此对象非彼对象。
-
首先我们需要一个模型演示。
-
需要执行一段代码(new对象)
public class Teacher {
public static void main(String[] args) {
int age = 1;
String name = "Name";
Test test = new Test();
test.testMethod(age , name);
}
}
class Test {
private int age;
private String name;
private static final int num = 2;
public void testMethod(int age, String name) {
this.age = age;
this.name = name;
}
}
看看他们到底干了些啥
为了加深理解,我们先来看一下类的生命周期,如图:
这里我们假设这是第一次使用该类,这样new一个对象就可以分成两个过程:类构造器完成类初始化和类实例化。
一、类加载过程(使用双亲委派模型加载)
1.加载,由类加载器负责根据一个类的全限定名来去取此类的二进制字节流到JVM内部,并存在运行时内存的方法区中(Teacher.class和Test.class)。
2.验证, 验证是否符合class文件规范。
3.准备,为类中的所有静态变量分配内存空间,并为其设置一个初始值。但是被final修饰的静态变量会直接赋值(Test中的num)。
4.解析, 虚拟机常量池内的符号引用替换为直接引用(地址引用)的过程,这个过程可以在初始化之后再执行。
5.初始化,为静态变量赋值,执行static代码块,注意先父类后子类。
连接阶段就是将加载到JVM的二进制字节流数据信息合并到JVM的运行状态中。
二、对象实例化
在Teacher.class文件中,找到main方法,开始执行main方法。将main方法在栈中开辟一个空间,称为栈帧。
public class Teacher {
public static void main(String[] args) {
int age = 1; // 在栈中分配内存空间
String name = "Name"; // 在栈和方法区中分配内存空间
Test test = new Test(); // 在栈中堆中分配内存空间
test.testMethod(age, name);
}
}
class Test {
private int age; // 在堆中分配内存空间
private String name; // 在堆中分配内存空间
private static final int num = 2; // 在堆和方法区中分配内存空间
public void testMethod(int age, String name) {
this.age = age;
this.name = name;
}
}
随后执行test.testMethod(age, name);这一行,在栈中新分配一个栈帧,调用test中的testMethod方法。
最后,testMethod方法执行完之后,testMethod栈帧从栈中释放空间,然后main方法执行完之后,main栈帧也释放空间,最后堆中的对象和方法区中的静态变量、字符串和字节码指令都没被使用时,根据java虚拟机的垃圾回收机制,进行对垃圾的回收。