类加载机制:从Class文件到内存中Java类型的过程。各个阶段时间段上能够有重叠。 类加载是在运行期间执行的,也描述为动态加载和动态链接。java
对于何时开始加载, 《Java虚拟机规范》没有强制约束。可是严格规定了有且只有六种状况,若是类没有初始化,必须当即对类进行"初始化" (加载、链接必然会先执行),称之为主动引用:数据库
不会触发类初始化的几个场景举例:数组
//场景一 经过子类引用父类的静态字段,不会致使子类初始化
public class SuperClass {
static { System.out.println("SuperClass init!");}
public static int value=123;
}
public class SubClass extends SuperClass{
static { System.out.println("SubClass init!");}
}
public class NoInitialization1 {
public static void main(String[] args) {
System.out.println(SubClass.value);
}
}
复制代码
//场景二 经过数组定义引用类,不会触发此类的初始化
public class NoInitialization2 {
public static void main(String[] args) {
SuperClass[] scarray=new SuperClass[10];
}
}
复制代码
//场景三 引用在编译期已被放入常量池的常量。
public class ConstClass {
static {
System.out.println("ConstClass init!");
}
public static final String HELLOWRLD="hello world";
}
public class NoInitialization3 {
public static void main(String[] args) {
System.out.println(ConstClass.HELLOWRLD);
}
}
复制代码
对于场景三咱们查看NoInitialization3的字节码发现,“hello world”已经在其常量池中,使用 ldc指令将常量压入栈中。而System.out则是使用getstatic指令。这个地方不涉及到ConstClass的初始化安全
Constant pool:
#4 = String #25 // hello world
#25 = Utf8 hello world
{
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #4 // String hello world
5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
复制代码
加载(loading):从静态文件到运行时方法区。完成三件事情:markdown
使用Java虚拟机内置的引导类加载器,或者用户自定义的类加载器。网络
确保字节流符合《Java虚拟机规范》的约束,代码安全性问题验证。验证是重要的,但不是必须的。 四个阶段:数据结构
一般状况下,为类变量(静态变量),分配内存并设置初始值(零值)。初值并非代码中赋的值123。123要等到初始化阶段。多线程
public static int value = 123;
复制代码
编译成class文件app
public static int value;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC
...
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
0: bipush 123
2: putstatic #2 // Field value:I
5: return
...
复制代码
某些状况下,设置初始值为456。好比final修饰的变量。由于变量值456,会提早加入到常量池。ide
public static final int value2 = 456;
复制代码
public static final int value2;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 456
复制代码
将常量池内的符号引用替换为直接引用的过程。 好比说这种,咱们要把 #2替换成实际的类引用,若是是未加载过的类引用,又会涉及到这个类加载过程。
getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
复制代码
执行类构造器()方法,非实例构造器()方法 。 ()方法:执行类变量赋值语句和静态语句块(static{})。顺序为其在源文件中顺序决定。 举例1:非法向前引用变量。 value的定义在 static{} 以后,只能赋值,不能读取值。
public class PrepareClass {
static {
value=3;
System.out.println(value);// value: illegal forword reference
}
public static int value=123;
}
复制代码
可是下面就能够
public class PrepareClass {
public static int value=123;
static {
value=3;
System.out.println(value);// value: illegal forword reference
}
}
复制代码
class文件参考
0: bipush 123
2: putstatic #2 // Field value:I
5: iconst_3
6: putstatic #2 // Field value:I
9: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
12: getstatic #2 // Field value:I
15: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
18: return
复制代码
举例2: ()执行顺序。子类初始化时,要先初始化父类
public class TestCInitClass2 {
static class Parent {
public static int A = 1;
static {
A = 2;
}
}
static class Sub extends Parent {
public static int B = A;
}
public static void main(String[] args) {
System.out.println(Sub.B);
}
}
复制代码
输出:
2
复制代码
Java虚拟机必须保证()方法在多线程环境下的同步问题。
实现“经过一个类的全限定名来获取其二进制字节流”的代码,称之为“类加载器”(Class Loader)。
类与其加载器肯定了这个类在Java虚拟机中的惟一性。
三层类加载器,绝大多数Java程序会用到如下三个系统提供的类加载器进行加载:
除了以上三个还有用户自定义的加载器,经过集成java.lang.ClassLoader类来实现。
加载Java的核心库,native代码实现,不继承java.lang.ClassLoader
URL[] urls= sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL url : urls) {
System.out.println(url);
}
结果输出:
file:../jdk1.8.0_73.jdk/Contents/Home/jre/lib/resources.jar
file:../jdk1.8.0_73.jdk/Contents/Home/jre/lib/rt.jar
file:../jdk1.8.0_73.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:../jdk1.8.0_73.jdk/Contents/Home/jre/lib/jsse.jar
file:../jdk1.8.0_73.jdk/Contents/Home/jre/lib/jce.jar
file:../jdk1.8.0_73.jdk/Contents/Home/jre/lib/charsets.jar
file:../jdk1.8.0_73.jdk/Contents/Home/jre/lib/jfr.jar
file:../jdk1.8.0_73.jdk/Contents/Home/jre/classes
复制代码
加载Java的扩展库,加载ext目录下的Java类
URL[] urls= ((URLClassLoader) ClassLoader.getSystemClassLoader().getParent()).getURLs();
for (URL url : urls) {
System.out.println(url);
}
结果输出:
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/sunec.jar
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/nashorn.jar
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/cldrdata.jar
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/jfxrt.jar
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/dnsns.jar
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/localedata.jar
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar
复制代码
加载Java应用的类。经过ClassLoader.getSystemClassLoader()来获取。
URL[] urls= ((URLClassLoader) ClassLoader.getSystemClassLoader().getParent()).getURLs();
for (URL url : urls) {
System.out.println(url);
}
结果输出:
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/sunec.jar
...
file:/.../jdk1.8.0_73.jdk/Contents/Home/lib/tools.jar
file:/.../java_sample/out/production/java_sample/ //这是咱们的应用程序
file:/Applications/IntelliJ%20IDEA.app/Contents/lib/idea_rt.jar
复制代码
ClassLoader.loadClass
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
复制代码
AppClassLoader,ExtClassLoader都继承URLClassLoader。 URLClassLoader.findClass(name)
protected Class<?> findClass(final String name)throws ClassNotFoundException {
// 一、安全检查
// 二、根据绝对路径把硬盘上class文件读入内存
byte[] raw = getBytes(name);
// 三、将二进制数据转换成class对象
return defineClass(raw);
}
复制代码
若是咱们本身去实现一个类加载器,基本上就是继承ClassLoader以后重写findClass方法,且在此方法的最后调包defineClass。 ** 双亲委派确保类的全局惟一性。 例如不管哪一个类加载器须要加载java.lang.Object,都会委托给最顶端的启动类加载器加载。
参考: 通俗易懂 启动类加载器、扩展类加载器、应用类加载器 深刻探讨 Java 类加载器
线程上下文类加载器(context class loader),能够从java.lang.Thread中获取。 双亲委派模型不能解决Java应用开发中遇到的全部类加载器问题。 例如,Java提供了不少服务提供者接口(Service Provider Interface,SPI),容许第三方提供接口实现。常见的SPI有JDBC,JCE,JNDI,JAXP等。SPI接口由核心库提供,由引导类加载器加载。 而其第三方实现,由应用类加载器实现。此时SPI就找不到具体的实现了。 SPI接口代码中使用线程上下文类加载器。线程上下文类加载器默认为应用类加载器。