一.java数据区
程序计数器,java虚拟机栈,本地方法栈,java堆,方法区,运行常量池(方法区的一部分),直接内存(非虚拟机运行时数据区的部分)
线程共享,线程私有
1.1 程序计数器
它是一块较小的内存空间,它的作用可以看做是当先线程所执行的字节码的信号指示器。
字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数 器完成
此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。
1.2 java虚拟机栈
生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方 法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。
注:java两种返回方式:return语句,抛出异常,这两种都会导致栈帧被弹出。
栈帧:保存着局部变量表,操作数栈,方法出入口等
局部变量表:存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,可能指向对象地址的引用指 针,也可能指向代表对象的句柄),returnAddress类型。
栈中所存储的变量和引用都是局部的(即:定义在方法体中的变量或者引用),局部变量和引用都在栈中(包括final的局部变量)
栈中还存储局部的对象的引用(定义在方法体中的引用类型的变量),对象的引用并不是对象本身,而是对象在堆中的地址,换句话说,局部的对象的引用所指对象 在堆中的地址在存储在了栈中
1.3 本地方法栈
和虚拟机栈发挥作用类似,一个是为java服务,一个是为本地方法服务,本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该方法的局部变量 表,操作数栈,动态链接,出口信息。
本地方法是指native方法,就是一个java调用非java代码的接口。该方法的实现由非java实现,如c
1.4 java堆
存在的意义就是存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存。这里面的对象被自动管理,也就是俗称的GC(Garbage Collector)所管理。
实例变量(非static修饰的成员变量)和对象关联在一起,所以实例变量也在堆中
java数组也在堆中开辟内存空间
1.5 方法区
共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。其中运行时常量池就是放在方法区中
类装载过程中产生的java.lang.class对象(Class对象)保存在方法区,而不是堆(new的实例对象)。
注:虚拟机通过装载、连接和初始化一个Java类型,使该类型可以被正在运行的Java程序所使用,也就是说要想使用一个类,必然会经历过上面的三个过程
垃圾回收主要是针对常量池回收和类型的卸载。
类信息:对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必须在方法区中存储下面类型信息。
这个类型的完整有效名称(全名=包名.类名)
这个类型直接父类的完整有效名称( java.lang.Object除外,其他类型若没有声明父类,默认父类是Object)
这个类型的修饰符(public、abstract、final的某个子集)
这个类型直接接口的一个有序列表
除此之外还方法区(Method Area)存储类信息还有类型的常量池( constant pool),域(Field)信息,方法(Method)信息
除了常量外的所有静态(static)变量
常量:static final修饰的成员变量(常量)都存储于 方法区(Method Area)中
静态变量:被static修饰的成员变量
静态变量又称为类变量,是因为静态变量和类关联在一起,随着类的加载而存在于方法区(而不是堆中)
八种基本数据类型(byte、short、int、long、float、double、char、boolean)的静态变量会在方法区开辟空间,并将对应的值存储在方法方法区,对于引用类型 的静态变量如果未用new关键字为引用类型的静态变量分配对象(如:static Object obj;)那么对象的引用obj会存储在方法区中,并为其指定默认值null;若对于引用 类型的静态变量如果用new关键字为引用类型的静态变量分配对象(如:static Person person = new Person();),那么对象的引用person 会存储在方法区中,并且该 对象在堆中的地址也会存储在方法区中(注意此时静态变量只存储了对象的堆地址,而对象本身仍在堆内存中)
即时编译器编译后的代码:
程序运行时会加载类编译生成的字节码,这个过程中静态变量(类变量)和静态方法及普通方法对应的字节码加载到方法区。
方法区中没有实例变量,这是因为,类加载先于对应类对象的产生,而实例变量是和对象关联在一起的,没有对象就不存在实例变量,类加载时没有对象,所以方 法区中没有实例变量
静态变量(类变量)和静态方法及普通方法在方法区(Method Area)存储方式是有区别的
1.6 运行常量池
常量:final修饰的成员变量表示常量,值一旦给定就无法改变
final修饰的变量有三种:静态变量、实例变量和局部变量
静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量 池,用于存放编译期生成的各种字面量(字符串,final常量值)和符号引用(类和接口的全限定名; 字段名称和描述符; 方法名称和描述符)
Java中八种基本类型的包装类的大部分都实现了常量池技术,它们是Byte、Short、Integer、Long、Character、Boolean,另外两种浮点数类型的包装类(Float、Double)则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值在-128到127时才在常量池。
1.7 直接内存
直接内存不是虚拟机运行时数据区的一部分
代码了解:
//局部变量p都在main方法的栈帧中 //new Person()对象在堆中分配空间 Person p = new Person(); //sum在栈中,new int[10]在堆中分配空间 int[] sum = new int[10]; class Person { //实例变量name和age在堆(Heap)中分配空间 private String name; private int age; //类变量(引用类型)name1和"cn"都在方法区(Method Area) private static String name1 = "cn"; //类变量(引用类型)name2在方法区(Method Area) //new String("cn")对象在堆(Heap)中分配空间 private static String name2 = new String("cn"); //num在堆中,new int[10]也在堆中 private int[] num = new int[10]; Person(String name,int age) { //this及形参name、age在构造方法被调用时 //会在构造方法的栈帧中开辟空间 this.name = name; this.age = age; } //setName()方法在方法区中 public void setName(String name) { this.name = name; } //speak()方法在方法区中 public void speak() { System.out.println(this.name+"..."+this.age); } }