java对象的生命周期可以分为7个阶段:创建阶段、使用阶段、不可视阶段、不可达阶段、可收集阶段、终结阶段、释放阶段
java类的生命周期分为7个阶段:加载、验证、准备、解析、初始化、使用、卸载
JVM
运行时数据区域
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域
有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是
依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存
将会包括以下几个运行时数据区域,如图2-1所示。
JDK7版本
在Hotspot虚拟机中,方法区的实现是在永久代里面,它里面主要存储运行时常量池、Klass类元信息等。
永久代属于JVM运行时内存中的一块存储空间,我们可以通过-XX:PermSize
来设置永久代的大小。
当内存不够的时候,会触发垃圾回收。
在 JDK7 里面,JVM运行时数据区是这样的:
JDK8版本
在Hotspot虚拟机中,取消了永久代,由元空间来实现方法区的数据存储
元空间不属于JVM内存,而是直接使用本地内存,因此不需要考虑GC问题
默认情况下元空间是可以无限制的使用本地内存的,但是我们也可以使用JVM参数 -XX:MetaspaceSize
来限制内存使用大小
1、程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地 址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)。此内存区域是唯 一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。
2、Java虚拟机栈
与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stack)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
经常有人把Java内存区域笼统地划分为堆内存(Heap)和栈内存(Stack),这种划分方式直接继承自传统的C、C++程序的内存布局结构,在Java语言里就显得有些粗糙了,实际的内存区域划分要比这更复杂。不过这种划分方式的流行也间接说明了程序员最关注的、与对象内存分配关系最密切的区域是“堆”和“栈”两块。其中,“堆”在稍后笔者会专门讲述,而“栈”通常就是指这里讲的虚拟机栈,或者更多的情况下只是指虚拟机栈中局部变量表部分。
局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
3、本地方法栈
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机
栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)
方法服务。
《Java虚拟机规范》对本地方法栈中方法使用的语言、使用方式与数据结构并没有任何强制规
定,因此具体的虚拟机可以根据需要自由实现它,甚至有的Java虚拟机(譬如Hot-Spot虚拟机)直接
就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失
败时分别抛出StackOverflowError和OutOfMemoryError异常。
4、堆
对于Java应用程序来说,Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。Java堆是被所
有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java
世界里“几乎”所有的对象实例都在这里分配内存。
5、方法区
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据
虽然《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫作“非堆”(Non-Heap),目的是与Java堆区 分开来
运行时常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是说,并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。
方法区的内部结构
《深入理解Java虚拟机 》书中 2.2.5 方法区 章节
对方法区存储内容描述如下:
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载 的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据
虽然《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫作“非堆”(Non-Heap),目的是与Java堆区 分开来
5.1 类型信息
对每个加载的类型( 类class、接口interface、枚举enum、注解annotation),JVM必 须在方法区中存储以下类型信息:
- 这个类型的完整有效名称(全名=包名.类名)
- 这个类型直接父类的完整有效名(对于interface或是java. lang.Object,都没有父类)
- 这个类型的修饰符(public, abstract, final的某个子集)
- 这个类型直接接口的一个有序列表
5.2 域信息(成员变量)
class MyAdd {
int count = 0; //成员变量/实例变量
static int sum = 0; //静态变量/类变量
}
- JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。
- 域的相关信息包括:域名称、 域类型、域修饰符(public, private,
protected, static, final, volatile, transient的某个子集)
5.3 方法信息
JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序:
- 方法名称
- 方法的返回类型(或void)
- 方法参数的数量和类型(按顺序)
- 方法的修饰符(public, private, protected, static, final,synchronized, native , abstract的一个子集)
- 方法的字节码(bytecodes)、操作数栈、局部变量表及大小( abstract和native 方法除外)
- 异常表( abstract和native方法除外)
每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
5.4 non-final的类变量
静态变量和类关联在一起,随着类的加载而加载,他们成为类数据在逻辑上的一部分
类变量被类的所有实例所共享,即使没有类实例你也可以访问它。
以下代码不会报空指针异常
public class MethodAreaTest {
public static void main(String[] args) {
Order order = null;
order.hello();
System.out.println(order.count);
}
}
class Order {
public static int count = 1;
public static final int number = 2;
public static void hello() {
System.out.println("hello!");
}
}
5.5 全局常量
static final 被声明为final的类变量的处理方法则不同,每个全局常量在编译的时候就被分配了。
5.6 运行时常量池
5.6.1 常量池
-
<