编译:即javac的过程,即把.java文件编译成.class文件,即编译成字节码文件,同时做一些类型以及格式的检查。
类只有在要运行的时候才会被加载进JVM,即编译后只有需要到这个类的时候才会把他加载进JVM运行,这种动态加载是依靠反射来实现的,一般来说一个class只会被加载一次,下一次就会从jvm的class缓存中获取,不会再去文件系统中去获取class文件了。
下面具体说说类的加载过程:
类的装载方式分两种:
1.隐式装载:即平时我们通过new产生对象时,他隐式的调用类装载器把类就加载进jvm中。无法进行动态加载,即你new的这个 class对象必须是你程序代码编译的时候有的。
2.显式装载:即利用Class.forname()来显示的加载一个类。即可以实现动态加载,你可以在程序运行时加载进一个编译时并没有的 类。
类的装载器(负责把类加载进jvm)
有三种类装载器,为什么要有三个类加载器,一方面是分工,各自负责各自的区块,另一方面为了实现委托模型,那么遇到要加 载某一个类,三个类装载器之间是如何工作的,具体见另一篇《深入理解java类加载器ClassLoader》。
Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:
- 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码(C++)来实现的,并不继承自
java.lang.ClassLoader
。 - 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
- 系统类加载器(APP class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过
ClassLoader.getSystemClassLoader()
来获取它。 下面这段代码的输出结果可以显示出classloader的树状组织结构。
类装载器是如何工作的,其实我们可以自己实现一个类加载器:
-
try {
-
URL url = new URL( "file:/d:/test/lib/" ); //根据路径
-
URLClassLoader urlCL = new URLClassLoader( new URL[]{url});
-
Class c = urlCL.loadClass("TestClassA" );
-
TestClassA object = (TestClassA)c.newInstance();
-
object.method();
-
}catch (Exception e){
-
e.printStackTrace();
-
}
从上面的代码应该能基本知道类加载器的实现。
接下来具体讲讲类加载的整个过程:
当我们想执行一个.class文件的时候,java.exe会帮助我们找到JRE,接着找到位于JRE中的jvm.dll,这就是java虚拟机,虚拟机激活后,会先做一些初始化的动作,一旦初始化动作完成后,就会产生第一个类加载器--Bootstrap Loader(它是由c++编写的),然后Bootstrap Loader加载Launcher.java中的ExtClassLoader加载器,并设定其parent为null(因为其parent是Bootstrap Loader,它是由c++编写的,无法找到这个实例),然后Bootstrap Loader再要求加载Launcher.java中的AppClassLoader加载器,并设定其parent为
ExtClassLoader。所以ExtClassLoader加载器和AppClassLoader加载器都是由Bootstrap Loader加载的,parent与是谁加载的并没有关系。
有了类加载器之后,那么这三个加载器之间如何协同工作,可以看《深入理解java类加载器ClassLoader》。至于是如何把.class文件加载进jvm的过程,从上面自定义一个类装载器也可以看出。
这样加载过程就差不多了,但是一个类要可以使用还要进行链接,初始化两个过程。
加载-->链接-->初始化 其中链接还包括验证-->准备--》解析三个过程
1、加载(即上面说的整个过程)
类的加载阶段,主要是获取定义此类的二进制字节流,并将这个字节流所代表的静态存储结构转化为方法区的运行时数据结 构,最后在Java堆中生成一个代表这个类的java.lang.Class对象作为方法区这些数据的访问入口。相对于类加载过程的其他 阶段,加载阶段是开发期可控性最强的阶段。我们可以通过定制不通的类加载器,也就是ClassLoader来控制二进制字节流的 获取方式。
2、验证
验证,准备和解析其实都属于连接阶段,而验证就是连接阶段的第一步。这一阶段主要是为了确保Class文件的字节流中包含 的信息复合当前虚拟机的要求,并且不会危害虚拟机自身的安全。主要验证过程包括:文件格式验证,元数据验证,字节码验 证以及符号引用验证。
3、准备
准备阶段正式为类变量分配内存并设置初始值。这里的初始值并不是初始化的值,而是数据类型的默认零值。这里提到的类 变量是被static修饰的变量,而不是实例变量。关于准备阶段为类变量设置零值的唯一例外就是当这个类变量同时也被final 修饰,那么在编译时,就会直接为这个常量赋上目标值。
4、解析
解析时虚拟机将常量池中的符号引用替换为直接引用的过程。
5、初始化
在准备阶段,变量已经赋过一次系统要求的初始值,在初始化阶段,则是根据程序员通过程序的主观计划区初始化类变量和其 他资源。Java虚拟机规范规定了有4种情况必须立即对类进行初始化(加载,验证,准备必须在此之前完成)
1)当使用new关键字实例化对象时,当读取或者设置一个类的静态字段(被final修饰的除外)时,以及当调用一个类的静态 方法时(比如构造方法就是静态方法),如果类未初始化,则需先初始化。
2)通过反射机制对类进行调用时,如果类未初始化,则需先初始化。
3)当初始化一个类时,如果其父类未初始化,先初始化父类。
4)用户指定的执行主类(含main方法的那个类)在虚拟机启动时会先被初始化。
除了上面这4种方式,所有引用类的方式都不会触发初始化,称为被动引用。如:通过子类引用父类的静态字段,不会导致子类 初始化;通过数组定义来引用类,不会触发此类的初始化;引用类的静态常量不会触发定义常量的类的初始化,因为常量在编 译阶段已经被放到常量池中了。
上面就是一个类加载并可以使用的整个过程,java的类加载这种只有需要的时候才加载进来的做法为内存节省了很大的空间。
Java自定义类加载器与双亲委派模型:https://www.cnblogs.com/wxd0108/p/6681618.html