Java类的生命周期

[TOC]

重要内存区域

方法区

运行时常量池、字段和方法信息、静态变量等数据。

已经加载的类信息、常量、静态变量以及方法代码的内存区域

堆区

Java堆用来存放对象实例,几乎所有的对象实例都在这里分配内存。Java堆存储的对象被垃圾收集器管理,这些受管理的对象无需也无法显式的销毁。

从内存回收的角度,Java堆可以粗略的分为新生代和老年代。从内存分配的角度Java堆中可能划分出多个线程私有的分配缓冲区。不管如何划分,Java堆存储的内容是不变的,进行划分是为了能更快的回收或者分配内存。

对象在堆内存的布局

  • 对象头
    • MarkWorld,用于存储对象运行时的数据,如hashcode,锁状态标志,GC分代年龄等
    • 元数据,指向方法区中目标类的类型信息,通过元数据指针确定对象的具体类型
  • 实例数据
    • 存储对象中的各种类型的字段信息
  • 用于对齐的padding

Java虚拟机栈

每一条Java虚拟机线程都有一个线程私有的Java虚拟机栈(Java Virtual Machine Stacks)。它的生命周期与线程相同,与线程是同时创建的。

存储线程中Java方法调用的状态,包括局部变量、参数、返回值以及运算的中间结果等。一个Java虚拟机栈包含了多个栈帧,一个栈帧用来存储局部变量表、操作数栈、动态链接、方法出口等信息。当线程调用一个Java方法时,虚拟机压入一个新的栈帧到该线程的Java栈中,当该方法执行完成,这个栈帧就从Java栈中弹出。我们平常所说的栈内存(Stack)指的就是Java虚拟机栈。

本地方法栈

支持Native方法,与JVM栈相似

程序计数器

为了保证程序能够连续地执行下去,处理器必须具有某些手段来确定下一条指令的地址,而程序计数器正是起到这种作用。

运行时数据区域

线程隔离

  1. Program Counter Register 程序计数器
  2. JVM Stacks 虚拟机栈
  3. Native Stack

线程共享

  1. Java Heap 堆
    1. 是java虚拟机所管理的内存中最大的一块,用来存放对象实例
    2. 可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可
    3. 可用过-Xmx和-Xms控制
  2. Method Area 方法区
    1. 存储加载过的类信息、常量、静态变量、即时编译器编译后的代码等数据
    2. 别名为Non-Heap(非堆),是PermentGeneration永久代
    3. 包含运行时常量池,用于存放各种字面量和符号引用,不要求常量只有编译器才能产生,比如String的intern

类的生命周期

Screen Shot 2017-01-16 at 20.38.17

Loading 加载

找到需要加载的类并把类的信息加载到方法区中,然后在堆区中实例化一个Class对象,作为方法区中这个类的信息的入口

  1. 根据类的全名,生成一份二进制的字节码
  2. 将字节码解析成方法区对应的数据结构
  3. 生成Class对象的实例表示该类

Linking 连接

不一定要等到加载完成后,有时可以同时执行(比如Loading和Verification可以同时执行)

Verification

保证类符合Java的语法规范。

比如:bytecode的完整性、final方法的检查、方法签名的检查

Preparation

JVM为类的成员变量分配内存空间并且赋予默认初始值,需要注意的是这个阶段不会执行任何代码,而只是根据变量类型决定初始值。即所有int赋值为0,代码中的初始化赋值不进行运行。

也可能会为有助于提高程序性能的数据结构分配内存,比如method table。

method table这个数据结构包含了指向所有类方法的指针(包括从父类集成的方法)

Resulotion

确认类、接口、属性和方法在类的run-time constant pool的位置,并且把这些符号引用替换为直接引用。

symbolic references ==》 direct reference

Screen Shot 2017-01-16 at 20.55.10

Initialization 初始化

如果一个类第一次直接引用,就会触发类的初始化

在此之前会先触发父类的初始化

过程

会按照代码书写顺序进行初始化,但总体规则是

  1. static先于非static
  2. 显式初始化,构造块初始化,最后调用构造函数进行初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Singleton {

private static Singleton mInstance = new Singleton();// 位置1,输出1,0
public static int counter1;
public static int counter2 = 0;

// private static Singleton mInstance = new Singleton();// 位置2,输出1,1

private Singleton() {
counter1++;
counter2++;
}

public static Singleton getInstantce() {
return mInstance;
}
}

public class InitDemo {

public static void main(String[] args) {

Singleton singleton = Singleton.getInstantce();
System.out.println("counter1: " + singleton.counter1);
System.out.println("counter2: " + singleton.counter2);
}
}

直接引用

  1. 创建实例(new、反射、cloning、反序列化)
  2. 调用类的static方法
  3. 使用或对类、接口的static属性进行赋值(不包括final的与在编译器确定的常量表达式,如果使用的父类的静态变量,则只需要加载父类即可)
  4. 调用API中的某些反射方法
  5. 子类被初始化
  6. 具有main方法的类

被动使用

  1. 引用父类的静态字段,只会引起父类的初始化,不会引起子类的初始化
  2. 定义类数组,不会引起类的初始化
  3. 引用类的常量,不会引起类的初始化

Unloading 卸载

满足下面的情况,类就会被卸载

  • 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class对象没有任何地方被引用,无法再任何地方通过反射访问该类的方法

ClassLoader

程序在启动的时候,并不会一次性加载所有要用的class文件,而是根据程序的需要,通过Java的类加载机制来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其他class文件所引用。所以ClassLoader就是用来动态加载class文件到内存当中的。

Bootstrap ClassLoader

Extension ClassLoader

App ClassLoader

0%