JVM 的内存划分中,有部分区域是线程私有的,有部分是属于整个 JVM 进程;我们将这部分归为一类。
1. 程序计数器(Program Counter Register),在 JVM 规范中,每个线程都有自己的程序计数器。这是一块比较小的内存空间,存储当前线程正在执行的 Java 方法的 JVM 指令地址,即字节码的行号。如果正在执行 Native 方法,则这个计数器为空。
2. Java 虚拟机栈(Java Virtal Machine Stack),同样也是属于线程私有区域,每个线程在创建的时候都会创建一个虚拟机栈,生命周期与线程一致,线程退出时,线程的虚拟机栈也回收。虚拟机栈内部保持一个个的栈帧,每次方法调用都会进行压栈,JVM 对栈帧的操作只有出栈和压栈两种,方法调用结束时会进行出栈操作。该区域存储着局部变量表,编译时期可知的各种基本类型数据、对象引用、方法出口等信息。
3. 本地方法栈(Native Method Stack)与虚拟机栈类似,本地方法栈是在调用本地方法时使用的栈,每个线程都有一个本地方法栈。
堆(Heap),几乎所有创建的 Java 对象实例,都是被直接分配到堆上的。堆被所有的
线程所共享,在堆上的区域,会被垃圾回收器做进一步划分,例如新生代、老年代的划分。
Java 虚拟机在启动的时候,可以使用“Xmx”之类的参数指定堆区域的大小。
方法区与堆一样,也是所有的线程所共享,存储被虚拟机加载的元(Meta)数据,包括类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区是一种 java 虚拟机的规范。由于方法区存储的数据和堆中存储的数据一致,实质上也是堆,因此,在不同的 JDK 版本中方法区的实现方式不一样。JDK7 以前,方法区就是堆中的“永久代”。
JDK7 开始去“永久代”,把静态变量、字符串常量池都挪到了堆内存中。JDK8 以后,“永久代”不存在了。存储的类信息、编译后的代码数据等已经移动到MetaSpace(元空间)中,元空间并没有处于堆内存上,而是(直接内存)直接占用的本
虽然,方法区几经变迁,但是对于我们学习的时候,仍然可以按照最初的规范讲解,先熟悉基本的概念,不必过细的深入底层。再细致的课程,可以参考百战程序员《架构课程:JVM 虚拟机优化》。
这是方法区的一部分。常量池主要存放两大类常量:
1. 字面量(Literal),如文本字符串、final 常量值。
2. 符号引用,存放了与编译相关的一些常量,因为 Java 不像 C++那样有连接的过程,
因此字段方法这些符号引用在运行期就需要进行转换,以便得到真正的内存入口地址。
直接内存并不属于 Java 规范规定的属于 Java 虚拟机运行时数据区的一部分。Java 的NIO 可以使用 Native 方法直接在 java 堆外分配内存,使用 DirectByteBuffer 对象作为这个堆外内存的引用。
为了让初学者顺利的分析内存,更加容易的体会程序执行过程中内存的变化,加深理解。我们将 JAVA 虚拟机内存模型进行简化。在本节课中,Java 虚拟机的内存可以简单的分为三个区域:虚拟机栈 stack、堆 heap、方法区 method area。
1. 栈描述的是方法执行的内存模型。每个方法被调用都会创建一个栈帧(存储局部变量、操作数、方法出口等)
2. JVM 为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)
3. 栈属于线程私有,不能实现线程间的共享!
4. 栈的存储特性是“先进后出,后进先出”
5. 栈是由系统自动分配,速度快!栈是一个连续的内存空间!
1. 堆用于存储创建好的对象和数组(数组也是对象)
2. JVM 只有一个堆,被所有线程共享
3. 堆是一个不连续的内存空间,分配灵活,速度慢!
1. 方法区是 JAVA 虚拟机规范,可以有不同的实现。
i.JDK7 以前是“永久代”
ii.JDK7 部分去除“永久代”,静态变量、字符串常量池都挪到了堆内存中
iii.JDK8 是“元数据空间”和堆结合起来。
2. JVM 只有一个方法区,被所有线程共享!
3. 方法区实际也是堆,只是用于存储类、常量相关的信息!
4. 用来存放程序中永远是不变或唯一的内容。(类信息【Class 对象,反射机制中会
重点讲授】、静态变量、字符串常量等)
public class MathodTest1 {
//属性
int id;
String name;
int age;
//方法
void study() {
System.out.println("好好学习,天天向上!");
}
//构造方法
MathodTest1(int id,String name,int age){
this.id=id;
this.age=age;
this.name=name;
}
public static void main(String[] args) {
MathodTest1 t1=new MathodTest1(1001,"谢谢",20);
// t1.id=1001;
// t1.name="谢谢";
// t1.age=20;
System.out.println(t1.age);
System.out.println(t1.name);
System.out.println(t1.id);
t1.study();
MathodTest1 t2=new MathodTest1(1002,"欣欣",12);
System.out.println(t2.age);
System.out.println(t2.name);
t2.study();
}
}
从图内存分配图可以得出如下结论:
同一类的每个对象有不同的成员变量存储空间。
同一类的每个对象共享该类的方法。
Java 中,方法中所有参数都是“值传递”,也就是“传递的是值的副本”。也就是说,我们得到的是“原参数的复印件,而不是原件”。因此,复印件改变不会影响原件。
传递的是值的副本。副本改变不会影响原件。
传递的是值的副本。但是引用类型指的是“对象的地址”。因此,副本和原参数都指向了同一个“地址”,改变“副本指向地址对象的值,也意味着原参数指向对象的值也发生了改变”。