类的装载经过加载 、连接(验证、准备、解析)、初始化过程,最后转化为Class对象,存在方法区中。
1、类在什么情况下才会被装载?
1、创建类的实例:new关键字,反射、克隆、反序列化。
2、当调用类静态方法:使用了字节码invokestatic指令。
3、当使用类的静态字段时(final常量除外),比如使用了getstatic或者putstatic指令。
4、初始化子类时要求先初始化父类。
5、含有main方法的那个类。
上面的几种情况都属于主动使用,除了主动使用还有被动使用,被动使用不会引起类的初始化。
主动使用示例:
public class Parent { static{ System.out.println("parent init"); } } public class Child extends Parent{ static { System.out.println("child init"); } } public class InitMain { public static void main(String[] args) { Child c = new Child(); } }
运行结果:
parent initchild init
使用new关键字创建类的实例会装载相关类,以及在初始化子类时,必须先初始化父类。
被动引用示例:
public class Parent { static{ System.out.println("parent init"); } public static int v = 100; } public class Child extends Parent { static { System.out.println("child init"); } } public class UseParent { public static void main(String[] args) { System.out.println(Child.v); } }
运行结果:
parent init100
虽然在UserParent中,直接访问了子类对象,但是child子类并未被初始化,只是父类Parent被初始化。可见引用一个字段时,只有直接定义该字段的类,才会被初始化。
注意:虽然Child类没有被初始化,但是已经被系统加载。
final常量示例:
public class ConstClass { static { System.out.println("ConstClass init"); } public static final String HELLWORLD = "hello world"; } public class TestV3 { public static void main(String[] args) { System.out.println(ConstClass.HELLWORLD); } }
运行结果:hello world
这里并没有打印ConstClass init,ConstClass并没有被初始化,原因是:javac在编译时,将常量直接植入目标类,不在使用被引用类。
2、完整加载过程
类的加载:指的是将类的。class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存中创建一个Java.lang.Class
对象(规范并没有说明Class对象位于哪里,HotSpot虚拟机将其放在了方法区中)用来封装类在方法区的数据结构。
验证:目的是保证加载的字节码的合法、合理并符合规范。
准备:正式为类变量分配内存并设置类变量初始值的阶段 。这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将 会在对象实例化时随着对象一起分配在Java堆中。 对于final修饰的常量,在该阶段就会赋值。
解析:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程 。
初始化:执行类的初始化方法<clinit>. 方法<clinit>是由编译器自动生成的,它是由类静态成员的赋值语句以及static语句块合并产生的。
来源:oschina
链接:https://my.oschina.net/suzheworld/blog/3163438