Java虚拟机类加载机制
一、类什么时候被加载?
并没有强制约束,交给了虚拟机自己去自由实现,HotSpot虚拟机是按需加载,在需要用到该类的时候加载这个类。
二、一个类的加载过程
.class文件被加载到虚拟机中,经历了:加载、验证、准备、解析、初始化、使用、卸载7个步骤,其中验证、准备、解析称为类的链接
1、加载:将classpath、jar包、网络、或者某个磁盘位置下的类的class二进制字节流读进来,在内存中生成一个代表这个类的.class对象,放入元空间
此阶段可以人工干预,自定义类加载器来实现类的加载。
2、验证:验证字节流是否符合Class文件格式的规范
3、准备:
准备阶段是正式为类中定义的变量(即静态变量)分配内存并设置变量的初始值,不包含实例变量,实例变量将会在对象实例化的时候跟随对象一起分配在堆中。例如一个类变量定义为 public static int value = 123,其初始值是0而不是123,把value赋值为123的putstatic指令是程序编译之后,存放于类构造器()方法之中。
//常量 public static final int a = 123 //赋正式值 //类变量 public static int b = 222; //赋默认值0 //实例变量 public int abc; //new对象的时候进行初始化
4、解析:虚拟机将常量池内的符号引用替换为直接引用的过程
5、初始化:当我们new一个类的对象、访问或修改一个类的静态变量(final修饰的类变量除外,因为final修饰的类变量,在编译器已经把结果放入常量池中);
调用一个类的静态方法、用反射API对一个类进行调用;
当初始化当前类,若其父类没有被初始化会首先初始化其父类;
当我们初始化一个类的时候我们的朱磊会被初始化(包含main()方法的那个类).…… 这些都会触发类的初始化。
6、使用:使用这个类
7、卸载:卸载的三个条件(一般情况下JVM是很少去卸载类的)
1)、该类所有实例都已经被垃圾回收
2)、加载该类的ClassLoader已经被垃圾回收
3)、该类的.class对象没有在任何地方被引用。例如不能在任何地方通过反射访问该类的方法
三、一个类被初始化的过程
类的初始化阶段,JVM才真正开始执行类中编写的java代码。
进行准备阶段时,变量已经赋过一次系统要求的初始零值,而在初始化阶段,才真正初始化类变量和其它资源。
public class Test01 { //静态常量 --准备阶段被执行 public static final String staticConstantField = "静态常量"; //静态变量 --准备阶段赋默认值null,初始化阶段赋值为 静态变量 public static String staticField = "静态变量"; //静态代码块 --初始化阶段执行 static{ System.out.println(staticConstantField); System.out.println(staticField); System.out.println("静态代码块"); }
//=========以下代码会在new对象的时候执行==============
//实例变量 --创建对象的时候赋值 public String field = "实例变量"; //初始化块 --创建对象的时候执行 { System.out.println(field); System.out.println("初始化块"); } //构造器 创建对象的时候执行 public Test01(){ System.out.println("构造器"); } public static void main(String[] args) { //当不执行任何操作时打印结果如下 //main为类中的静态方法,这时候调用main会初始化当前类 /** * 静态常量 * 静态变量 * 静态代码块 */ //new对象,首先加载当前类,然后创建对象 //这时候会为实例变量、初始化代码块、构造方法进行赋值 new Test01(); /** * 静态常量 * 静态变量 * 静态代码块 * 实例变量 * 初始化块 * 构造器 */ } }
四、继承时父子类的初始化顺序是怎么样的?
顺序如下:
父类--静态变量 父类--静态初始化块 子类--静态变量 子类--静态初始化块 父类--变量 父类一初始化块 父类-一构造器 子类--变量 子类--初始化块 子类一构造器
五、究竟什么是类加载器
在类“加载”阶段,通过一个类的全限定名来获取描述该类的二进制字节流的这个动作的“代码”被称为“类加载器"(Class Loader),这个动作是可以自定义实现的;
作用:读取.class字节码文件,放入JVM内存中
六、JVM有哪些类加载器
站在Java虚拟机的角度来看,只存在两种不同的类加载器:
1、启动类加载器(Bootstrap ClassLoader),使用C++语言实现,是虚拟机自身的一部分;
2、其他所有的类加载器,由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象I类java.lang.ClassLoader;
站在Java开发者的角度来看,自JDK1.2开始,Java一直保持着三层类加载器架构;
七、JVM不同的类加载器加载哪些文件
1、启动类加载器(Bootstrap ClassLoader):(根的类加载器)
1)加载java核心库下的jar包:<JAVA_HOME>jrelib 里面的rt.jar,resources.jar、charsets.jar等jar包, 但并不是lib中所有的jar都由根加载器加载
2)被-Xbootclasspath参数所指定的路径中存放的类库(用的不多);
由于引导类加载器涉及到虚拟机本地实现,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作
String、Object、ArrayList….类都是由启动类加载器加载
2、扩展类加载器(Extension ClassLoader):
sun.misc.Launcher$ExtClassLoader,
1)〈JAVAHOME>jrelibext,
2)被java.ext.dirs系统变量所指定的路径中所有的类库(用的不多);
3、应用程序类加载器(Application ClassLoader):系统的类加载器
sun.misc.Launcher$AppClassLoader
1)加载用户名类路径(ClassPath)上所有的类库 --也就是自己写的代码
2)项目中所依赖的第三方jar包
八、JVM三层类加载器之间的关系是继承吗
不是继承关系,ExtClassLoader和AppClassLoader是rt.jar中Launcher中的两个内部类,都继承URLClassLoader,
且BootstrapClassLoader是用C++语言实现的,EXt、APPClassLoader用java语言实现的,不存在继承
如果自定义类加载器,它会继承ClassLoader,而不是继承AppClassLoader或者ExtClassLoader
九、你了解JVM类加载的双亲委派模型吗
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当上一层类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到这个类)时,下一层类加载器才会尝试自己去加载;
十、JDK为什么要设计双亲委派模型,有什么好处
1、确保jdk安全,避免Java核心类库被修改;
2、避免重复加载;
3、保证类的唯一性;
如果你写一个jaa.lang.String的类去运行,发现会抛出如下异常;
十一、可以打破JVM双亲委派模型吗,如何打破JVM双亲委派模型
可以;想要打破这种模型,那么就自定义一个类加载器,重写其中的loadClass方法,使其不进行双亲委派即可;双亲委派的核心代码就在loadClass方法中
十二、如何自定义自己的类加载器
1、继承ClassLoader
2、覆盖findClass(String name)方法 或者 loadClass() 方法;
findClass(String name)方法 不会打破双亲委派;
loadClass() 方法 可以打破双亲委派;
十三、ClassLoader中的loadClass()、findClass()、defineClass()区别?
loadClass() 就是主要进行类加载的方法,默认的双亲委派机制就实现在这个方法中;
findClass() 根据名称或位置加载.class字节码;
definclass() 把字节码转化为java.lang.Class;
1、当我们想要自定义一个类加载器的时候,并且想破坏双亲委派模型时,我们会重写loadClass()方法;
2、如果我们想定义一个类加载器,但是不想破坏双亲委派模型的时候呢?可以可以重写findClass方法(),findClass()方法是JDK1.2之后的ClassLoader新添加的一个方法,这个方法只抛出了一个异常,没有默认实现;
JDK1.2之后已不再提倡用户直接覆盖loadClass()方法,而是建议把自己的类加载逻辑实现到findClass()方法中;
所以,如果你想定义一个自己的类加载器,并且要遵守双亲委派模型,那么可以继承ClassLoader,并且在findClass()中实现你自己的加载逻辑即可;
十四、Tomcat的类加载机制
可以看到,在原来的Java的类加载机制基础上,Tomcat新增了3个基础类加载器和每个Web应用的类加载器+JSP类加载器;
3个基础类加载器在 conf/catalina.properties 中进行配置:
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=
tomcat6以后把common、catalina和shared都合并为lib目录。
Tomcat自定义了WebAppClassLoader类加载器,打破了双亲委派的机制,即如果收到类加载的请求,首先会尝试自己去加载,如果找不到再交给父加载器去加载,目的就是为了优先加载Web应用自己定义的类,我们知道ClassLoader默认的loadClass方法是以双亲委派的模型进行加载类的,那么Tomcat打破了这个规则,重写了loadClass方法,我们可以看到
WebAppClassLoader类中重写了loadClass方法;
十五、为什么tomcat要打破双亲委派模型
Tomcat是web容器,那么一个web容器可能需要部署多个应用程序;
1、部署在同一个Tomcat上的两个Web应用所使用的Java类库要相互隔离;
2、部署在同一个Tomcat上的两个Web应用所使用的Java类库要互相共享;
3、保证Tomcat服务器自身的安全不受部署的Web应用程序影响;
4、需要支持JSP页面的热部署和热加载;
十六、热加载、热部署,如何实现热加载
热加载是指可以在不重启服务的情况下让更改的代码生效,热加载可以显著的提升开发以及调试的效率,它是基于Java的类加载器实现的,但是由于热加载的不安全性,一般不会用于正式的生产环境;
热部署 是指可以在不重启服务的情况下重新部署整个项目,比如Tomcat热部署就是在程序运行时,如果我们修改了War包中的内容,那么Tomcat就会删除之前的War包解压的文件夹,重新解压新的War包生成新的文件夹;
1、热加载是在运行时重新加载class,后台会启动一个线程不断检测你的class是否发生改变;
2、热部署是在运行时重新部署整个项目,耗时相对较高;
如何实现热加载呢?
在程序代码更改且重新编译后,让运行的进程可以实时获取到新编译后的class文件,然后重新进行加载;
1、实现自己的类加载器;
2、从自己的类加载器中加载要热加载的类;
3、不断轮询要热加载的类class文件是否有更新,如果有更新,重新加载;