在jvm知识总结的时候,看了不少博客,发现自己学习的还不扎实,所以学习一下别人的博客,记录下
参考 Java 类加载机制(阿里面试题)-何时初始化类
类加载机制
类从被加载到虚拟机内存开始,到卸载出内存为止,整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段
其中加载、验证、准备、初始化、和卸载这5个阶段的顺序是确定的。而解析阶段不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java的运行时绑定。
需要明确的是初始化 执行初始化 静态变量,并不会执行 执行类似构造器等方法,根据程序员通过程序制定的主观计划初始化类变量和其他资源。初始化过程其实是执行类构造器()方法的过程
关于初始化:JVM规范明确规定,有且只有5种中情况必须执行对类的初始化(加载、验证、准备自然再此之前要发生):
- 遇到new,getstatic,putstatic,invokestatic这失调字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候
- 使用java.lang.reflect包的方法对类进行反射调用时,如果类没初始化,则需要初始化
- 当初始化一个类时,如果发现父类没有初始化,则需要先触发父类初始化。
- 当虚拟机启动时,用户需要制定一个执行的主类(包含main函数的类),虚拟机会先初始化这个类。
- 但是用JDK1.7启的动态语言支持时,如果一个MethodHandle实例最后解析的结果是REF_getStatic、REF_putStatic、Ref_invokeStatic的方法句柄时,并且这个方法句柄所对应的类没有进行初始化,则要先触发其初始化。
通过子类来引用父类的静态字段,不会导致子类初始化,而且只是初始化,没有执行任何的构造器语句!
class SSClass
{
static
{
System.out.println("SSClass");
}
public SSClass() {
System.out.println("SSClass init");
}
}
class SuperClass extends SSClass
{
static
{
System.out.println("SuperClass init!");
}
public static int value = 123;
public SuperClass()
{
System.out.println("init SuperClass");
}
}
class SubClass extends SuperClass
{
static
{
System.out.println("SubClass init");
}
static int a;
public SubClass()
{
System.out.println("init SubClass");
}
}
public class NotInitialization
{
public static void main(String[] args)
{
System.out.println(SubClass.value);
}
}
执行结果:
SSClass
SuperClass init!
123
通过数组定义来引用类,不会触发此类的初始化
public class NotInitialization
{
public static void main(String[] args)
{
SuperClass[] sca = new SuperClass[10];
}
}
没有任何的输出
<clinit>()方法对于类或者接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,
那么编译器可以不为这个类生产<clinit>()方法。
接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成<clinit>()方法。
但接口与类不同的是,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。只有当父接口中定义的变量使用时,
父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。
利用静态内部类实现线程安全的单例模式
虚拟机会保证一个类的 <clinit>( )方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执
行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。如果在一个类的<clinit>()方法中有
耗时很长的操作,就可能造成多个线程阻塞,在实际应用中这种阻塞往往是隐藏的。
static class DeadLoopClass
{
static
{
if(true)
{
System.out.println(Thread.currentThread()+"init DeadLoopClass");
while(true)
{
}
}
}
}
public static void main(String[] args)
{
Runnable script = new Runnable(){
public void run()
{
System.out.println(Thread.currentThread()+" start");
DeadLoopClass dlc = new DeadLoopClass();
System.out.println(Thread.currentThread()+" run over");
}
};
Thread thread1 = new Thread(script);
Thread thread2 = new Thread(script);
thread1.start();
thread2.start();
}
}
执行结果:
Thread[Thread-0,5,main] start
Thread[Thread-1,5,main] start
Thread[Thread-0,5,main]init DeadLoopClass
需要注意的是,其他线程虽然会被阻塞,但如果执行<clinit>( )方法的那条线程退出<clinit>( )方法后,其他线程唤醒之后
不会再次进入<clinit>( )方法。同一个类加载器下,一个类型只会初始化一次。
public class DealLoopTest
{
static class DeadLoopClass
{
static
{
if(true)
{
System.out.println(Thread.currentThread() + "init DeadLoopClass");
try
{
TimeUnit.SECONDS.sleep(10);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
public static void main(String[] args)
{
Runnable script = new Runnable(){
public void run()
{
System.out.println(Thread.currentThread()+" start");
DeadLoopClass dlc = new DeadLoopClass();
System.out.println(Thread.currentThread()+" run over");
}
};
Thread thread1 = new Thread(script);
Thread thread2 = new Thread(script);
thread1.start();
thread2.start();
}
}
Thread[Thread-0,5,main] start
Thread[Thread-1,5,main] start
Thread[Thread-1,5,main]init DeadLoopClass (之后sleep 10s)
Thread[Thread-1,5,main] run over
Thread[Thread-0,5,main] run over