前言
一个完整即将使用的类已经在JVM虚拟机中整装待发了,进击!!!
类的使用
类的使用一般可以分为主动使用和被动使用;
主动使用
- 创建类的实例: new关键字、类工厂等
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射
- 初始化一个类的子类
- main方法
- 动态语言支持(JDK1.7开始提供)
被动使用
除了以上7种外的所有调用,都是被动调用,都不会对类进行初始化,也就是只会进行类的加载、解析;
案例1
public class Test {
public static void main(String[] args) {
①------System.out.println(Child.str);
}
}
class Parent{
public static String str = "Parent";
static {
System.out.println("Parent 静态代码块");
}
}
class Child extends Parent{
public static String str2 = "Child ";
static {
System.out.println("Child 静态代码块");
}
}
输出:
Parent 静态代码块
Parent
将①换成System.out.println(Child.str2)
输出:
Parent 静态代码块
Child 静态代码块
Child
解析
- 第一次输出虽然调用了Child.str,但实际上没有对Child进行主动使用,所以不会被初始化,不会输出静态代码块中的内容
- 第二次输出时,
Child
是主动使用,Parent
因为是Child
的父类,所以也初始化,所以子类初始化,父类也会初始化
案例2
public class Test {
public static void main(String[] args) {
System.out.println(Parent.uuid);
}
}
class Parent {
public static final String uuid= UUID.randomUUID().toString();
static {
System.out.println("parent4 static block");
}
}
解析
uuid是静态常量,但是在编译期uuid的值并不能确定,这个值就不会被放到常量池中,所以这个类的初始化会在被主动调用时触发。
总结
- 类的初始化一定是首次、主动使用该类
- 子类初始化,父类也会初始化,并且父类先初始化完毕(不适用于接口)
- final修饰的常量,在编译阶段,就会被放在调用这个常量的方法的所在的类的常量池,调用类并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化;如果final定义的值随机产生或每次都会改变,则该值不会放入调用它的方法的常量池中,所以每次调用依旧触发该对象所在的类(主动使用),该类即被初始化
- 当一个接口在初始化时,并不要求其父接口完成了初始化,只有在真正使用到父类接口的时候(如引用接口中定义的常量时),父类才会被初始化
常用java虚拟机参数(类加载/卸载跟踪)
-verbose:class 跟踪类的加载和卸载
-XX:+TraceClassLoading 跟踪类的加载
-XX:+TraceClassUnloading 跟踪类的卸载
-XX:+PrintClassHistogram 查看系统中类的分布情况
(需要在控制台按下Ctrl+Break组合键,就会出现柱状图,可以看到当前系统中占用空间最多的对象,以及其实例数量和空间大小 )
网友评论