这里写目录标题
- java的生命周期
- 1. 加 载
- 2. 连接
- 3. 初始化
- 4. 使用
- 5. 类卸载
- 静态代码、变量、方法
- 静态变量
- 静态方法
- 静态代码块
- 静态内部类
- 内部类相关问题
- 静态内部类,为什么每次实例化是不同的对象
- java静态内部类的延迟加载,以及内部类里面的静态元素的加载时机
java的生命周期
java的生命周期为:装载、连接、初始化、使用和卸载
1. 加 载
一个java类的代码,经过编译之后生成一个后缀为.class的文件,java虚拟机能识别这种文件。
java的生命周期就是class文件从加载到消亡的过程。
关于加载,就是将源文件的class文件找到类的信息将其加载到方法区中,然后再堆中实例化一个java.lang.Class对象,作为方法区这个类的信息的入口。但是这一功能是在JVM之外实现的,主要的原因是方便让应用程序自己决定如何获取这个类,在不同的虚拟机加实现的方式不一定相同,hotspot虚拟机是采用需要时再加载的方式,也有其他是先预先加载的。
2. 连接
连接一般是加载阶段和初始化阶段交叉进行,过程由以下三部分组成
- 验证:确定该类是否符合java语言的规范,有没有属性和行为的重复,继承是否合理,总之,就是保证jvm能够执行
- 准备:主要做的就是为由static修饰的成员变量分配内存,并设置默认的初始值
默认初始值如下:
1. 八种基本数据类型的初始值是0
2. 引用类型默认的初始值为null
3. 有static final 修饰的会直接赋值,例如:static final int x = 10; 则默认就是10 - 解析:这一阶段的任务就是把常量池中的符号引用转换成直接引用,说白了就是jvm会将所有的类或者接口名、字段名、方法名转换为具体的内存地址
3. 初始化
初始化这个阶段是将静态变量(类变量)赋值的过程,即只有static修饰的才能被初始化,执行的顺序是:父类静态域或者静态代码块,然后是子类静态域或者子类静态代码块(静态代码块先被加载,然后再是静态属性)
4. 使用
在类的使用过程中依然存在以下三步
- 对象实例化:就是执行类中构造函数的内容,如果该类存在父类,JVM会通过显示或者隐式的方式先执行父类的构造函数,在堆内存中为父类的实例变量开辟空间,并赋予默认的初始值,然后在根据构造函数的代码内容将真正的值赋予实例变量本身,然后,引用变量获取对象的首地址,通过操作对象来调用实例变量和方法
- 垃圾收集:当对象不再被引用的时候,就会被虚拟机标上特别的垃圾记号,在堆中等待GC回收
- 对象的终结:对象被GC回收后,对象就不再存在,对象的生命也就走到了尽头
5. 类卸载
类卸载即类的生命周期走到了最后一步,程序中不再有该类的引用,该类也就会被JVM执行垃圾回收,从此生命结束
静态代码、变量、方法
静态代码在类的初始化阶段被初始化
非静态代码则在类的使用阶段(也就是实例化一个类的时候)才会被初始化
静态变量
静态变量理解为类变量(与对象无关)
实例变量则属于一个特定的对象。
静态变量只在内存中申请一次空间。如果这块改了,其他地方就会收到影响
静态变量有两种情况
- 静态变量是基本数据类型。这种情况下在类的外部不必创建该类的实例就可以直接使用
- 静态变量是一个引用。这种情况比较特殊,主要问题是由于静态变量是一个对象的引用,那么必须初始化这个对象之后才能将引用指向它。
因此如果一个引用定义为static的时候,就必须在定义的时候就对其对象进行初始化
public class TestForStaticObject{
static testObject o = new testObject (); //定义一个静态变量并实例化
public static void main(String args[]){
//在main中直接以“类名.静态变量名.方法名”的形式使用testObject的方法
}
}
静态方法
与类变量不同,方法(静态方法与实例方法)在内存中只有一份,无论该类有多少个实例,都共用一个方法
静态方法与实例方法的不同:
- 静态方法可以直接使用,而实例方法必须类实例化之后通过对象来调用
- 在外部调用静态方法时,可以使用** 类名.方法名** 或者 对象名.方法名 的形式,而实例方法只能用 对象名.方法名
- 静态方法只能访问静态成员。而实例方法中可以访问静态成员和实例成员
- 静态方法中不能使用this(因为this是与实例相关的)
静态代码块
静态代码块主要用于类的初始化。只执行一次,并且在同属于一个类的main函数之前执行。
静态代码块的特点:
- 静态代码块会在类被加载时自动执行
- 静态代码块只能定义在类中,不能定义在方法中
- 静态代码块中的变量都是局部变量,只在块内有效
- 一个类中可以定义多个静态代码块,按顺序执行
- 静态代码块只能访问类的静态成员,而不允许访问实例成员
静态代码块和静态函数的区别
静态代码块:一般情况下,如果有些代码必须在项目启动前就执行的时候,需要使用静态代码块,这种代码是主动执行的,它只执行一次,并且在同属于一个类的main函数之前执行
静态函数:需要在项目启动的时候就初始化,在不创建对象的情况下,其他程序来调用的时候,需要使用静态方法,这种代码是被动的
静态内部类
- 静态内部类是由static修饰的内部类(普通的类无法用static关键字修饰)
- 静态内部类也是类,在继承和实现接口方面,和普通的类都是一样的
- 外部类可以访问静态内部类的private属性。静态内部类,不能访问外部类的非静态的方法和属性,(如果在静态类的方法里,实例化了外部类,通过引用,静态内部类可以访问外部类的private属性)
- 静态内部类可以被实例化,外部类每次实例化都会创建一个新的静态内部类对象,不管外部类的状态(是否创建),可以直接使用它的内部类
5. 静态内部类不会常驻内存,静态变量和方法才会常驻内存的方法区
内部类相关问题
静态内部类,为什么每次实例化是不同的对象
public class Test01 {
static final class InnerTest{
static final InnerTest in = new InnerTest();
}
public static void main(String[] args) {
InnerTest iin1 = new Test01.InnerTest();
System.out.println(iin1 );
InnerTest iin2 = new Test01.InnerTest();
System.out.println(iin2 );
System.out.println(iin1.in);
System.out.println(iin2.in);
}
}
输出结果为
com.lvbu.Test01$InnerTest@45ee12a7
com.lvbu.Test01$InnerTest@330bedb4
com.lvbu.Test01$InnerTest@2503dbd3
com.lvbu.Test01$InnerTest@2503dbd3
因为对于每次每个new Test01来说,都要创建一个新的innerTest
它是静态的,但是因为Test01有很多,所以不是全局唯一的,而是每个Test01实例中唯一的。
参考博客:
java静态内部类的延迟加载,以及内部类里面的静态元素的加载时机
public class Outer {
private Outer() {
System.out.println("outer instance create");
}
static {
System.out.println("outer init");
}
public static void main(String[] args) {
System.out.println("outer main execute");
// Outer outer = Inner.outer;
}
private static class Inner {
static {
System.out.println("inner init");
}
private static final Outer outer = new Outer();
}
}
运行以上代码,输出结果为下,证明当项目启动,静态内部类以及内部类里面的静态元素不会被初始化
outer init
outer main execute
当把 Outer outer = Inner.outer; 这一行取消注释后,输出结果如下,证明只有当访问静态内部类里面的静态元素的时候,静态内部类以及里面的静态元素才会被初始化
outer init
outer main execute
inner init
outer instance create
当把main方法换成这样的时候
public static void main(String[] args) {
System.out.println("outer main execute");
Outer outer = new Outer();
}
运行结果如下,证明,外部类初始化,静态内部类并没有初始化
outer init
outer main execute
outer instance create
当把静态内部类里面的属性变成Integer,不让它实例化外部类,并且main方法访问这个属性的时候,具体代码如下
public class Outer {
private Outer() {
System.out.println("outer instance create");
}
static {
System.out.println("outer init");
}
private static class Inner {
static {
System.out.println("inner init");
}
private static final Integer outer = 3;
}
public static void main(String[] args) {
System.out.println("outer main execute");
Integer outer = Inner.outer;
}
}
输出结果如下,证明当访问静态内部类以及静态内部类的静态元素的时候,静态内部类会初始化,而外部类并没有初始化,证明静态内部类的初始化与外部类的初始化无关
outer init
outer main execute
inner init
结论:
- 静态内部类以及静态内部类里面的静态元素,不会随项目启动而被加载。
- 静态内部类不会依赖于外部类的初始化而初始化(静态内部类初始化与外部类无关,外部类初始化,静态内部类不会初始化。 静态内部类初始化了,外部类也不会初始化,两者没联系)
- 当访问静态内部类里面的静态元素的时候,静态内部类以及里面的静态元素才会被初始化