***.class文件执行大概就是这样来走的。我们都知道我们的java文件经过编译以后会生成对应的class文件。先经过类装载子系统,然后塞进运行时内存模型的元空间,开始执行方法,对象放在堆,线程开辟栈空间,程序计数器控制执行顺序。字节码执行引擎整体调控程序计数器,走你。。。大概就是这样的。我们先来看一下类装载子系统是如何工作的。
类装载子系统大概分为,验证->准备->解析->初始化。笼统的来说就这个4个步骤。
1,验证:验证我们的编译文件(字节码文件)是否正确。
2,准备:给予类的静态常量开辟堆空间。并且赋予默认值。对象也在这个时候放置在堆空间,并且给予空值。
3,解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用。就像是我们把main转化为001,将()转化为002,将这一系列的编码存在堆上。
4,初始化,将第3步的静态常量(或对象)赋值,执行静态代码块。
类的加载器大致分为,启动类加载器,扩展类加载器,应用类加载器和自定义加载器,后面我们会说如何实现自己的类加载器。
启动类加载器是用来加载java自身的lib包的。用C语言实现的,我们是看不到的。
扩展类加载器顾名思义,是加载java的扩展包的。加载ext包下的jar包
然后就是我们的应用加载器,来执行我们一行行代码的。
最后才是我们的自定义加载器。我来看一段代码。
import com.sun.crypto.provider.DESKeyFactory; public class Main { public static void main(String[] args) { System.out.println(String.class.getClassLoader()); System.out.println(DESKeyFactory.class.getClassLoader().getClass().getName()); System.out.println(Main.class.getClassLoader().getClass().getName()); } }
输入如下:
null sun.misc.Launcher$ExtClassLoader sun.misc.Launcher$AppClassLoader Process finished with exit code 0
三种加载器就是这样的。
双亲委派机制:
我们知道一个基础的知识,就是我们新建的java.lang.String是无法加载的,就是加载过程的双亲委派机制限制了我们自定义重写java本来的代码。
双亲委派是为了阻止我们重写java内部的类,做到了沙箱安全的目的。
大概就是这样来实行的。上图:
自定义加载器会优先拿到要加载的文件,但是他不会去马上加载。而是直接交给应用类加载器,应用类加载器也不会管,继续向上走,交给扩展类加载器,他也不管,继续向上走,交给启动类加载器,没办法了,启动类加载器没法继续向上交付了。自己先试试可以加载吗?可以加载就加载,加载不了退返给扩展类加载器,扩展类看到是推回来的,试试吧。可以加载吗?可以加载就加载,加载不了退返给应用类加载器,应用类加载器可以加载就加载,加载不了退返给自定义加载器。这样一个由下到上,谁也不管。逐个去尝试往下推的方法去加载。好久就是为了防止你重写java内部的类。
这里简单说一下自定义加载器。
我们只要重写com.sun.org.apache.bcel.internal.util;包下的ClassLoader类的findClass方法,最后调用defineClass方法。就可以实现我们的自定义加载器。
这回我们再回过头来看上一篇博客的tomcat打破双亲委派机制也就懂得是怎么回事了吧。不懂的评论留言吧。就说到这里,我们下一次说一下jvm运行时内存模型那一块。
写的这么不好的文章能坚持读到最下面确实挺不易的,贡献一个小技巧,来看类是否真的被加载了。