类加载过程(加载——链接——初始化——使用——卸载)
类的加载详细过程:
加载——验证——准备——解析——初始化——使用——卸载
1.加载【将硬盘中的classpath路径下的class文件以及jar包以及指定的class文件的class二进制字节流读取进来,在内存中生成一个代表这个类的java.lang.class对象放入元空间(metaspace),此阶段可使用自定义类加载器来进行人工干预】
2.验证【验证class文件的字节流文件是否符合《java虚拟机规范》,保证虚拟机的安全】
3.准备【类变量赋初始值,比如int赋值为0,long为0L,boolean为false,引用类型为null,(但是常量是在此准备阶段就赋正式的值,静态变量和实例变量都只赋初始值,此处要与类的初始化做区别)】
4.解析【把符号引用翻译为直接引用】
5.初始化【当new一个类的对象,访问类的静态属性,修改类的静态属性,调用类的静态方法,用反射API对类进行调用,初始化当前类时当前类的父类也会被初始化(父类先初始化,在初始化当前类),上述这些场景都会触发类的初始化;】
6.使用【使用这个类】
7.卸载【触发卸载的条件比较苛刻,一般不会卸载,类卸载的情况有1.该类所有的实例都已被GC回收,2加载该类的classLoader已被回收,3.该类的java.lang.Class对象没有任何引用,不能在任何地方通过反射访问这个类】
其中第2,3,4步合并称为链接,所以类加载机制一般都称为(加载——链接——初始化——使用——卸载)
类的准备阶段,常量(static final修饰)赋实际值,其他属性都只赋初始值
类的初始化阶段时静态变量(static修饰)赋实际值,执行静态代码块(static{####业务逻辑代码####})
创建实例对象时,执行构造方法,执行普通代码块,类的普通变量(没有 static和final修饰的)赋实际值
继承时父子类的初始化顺序
父类——静态变量
父类——静态代码块
子类——静态变量
子类——静态代码块
父类——普通变量
父类——普通代码块
父类——构造方法
子类—。。。。。
类加载机制
什么是类加载器?
在类的加载阶段,执行通过类的全限定名获取描述类的二进制字节流的这个动作的代码,被称为类加载器(ClassLoader)
jvm有哪些类加载器?
子JDK1.2开始java一直保持这三层类加载器结构(这三层类加载器并不是继承关系)
1.启动类加载器(Bootstrap Classloader),C++实现的,虚拟机的一部分,最顶级的类加载器,负责加载
<JAVA_HOME>\jre\lib\rt.jar,resources.jar.charsets.jar以及被-Xbootclasspath参数指定的路径中存放的类库
2.扩展类加载器(ExtClassLoader) Extension ClassLoader
负责加载<JAVA_HOME>\jre\lib\ext,被java.ext.dirs系统变量所指定的路径中的所有类库
3.应用程序类加载器(AppClassLoader) Application ClassLoader,系统的类加载器,负责加载classpath下的class文件,也是比较常用的类加载器,一般我们用的依赖包都是被它加载
查看类的是被哪个加载器加载的
调用类名.class.getClassLoader()
ExtClassLoader和AppClassLoader都是Launcher这个类的静态内部类,都继承了URLClassLoader这个类,URLClassLoader继承了SecureClassLoader,SecureClassLoader继承了ClassLoader(抽象类)
4.自定义类加载器(Custom ClassLoadeer)
1、继承 ClassLoader
2、重写findClass(String name)方法 或者 loadClass() 方法
二者的区别是findClass不会打破双亲委派,loadClass重写后可以打破双亲委派
双亲委派模型(java类加载机制采用双亲委派)
双亲委派模型的工作过程是: 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当上一层类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到这个类)时,下一层类加载器才会尝试自己去加载;
分析源码时发现,在类加载时,类加载器都是通过继承关系调用的ClassLoder这个classLoader方法,每次在加载的时候并不是直接就委派给上层也就是parent类去加载的,而是先从原空间中检查是不是已经加载过了,如果找不到则再委派给上层加载
JDK 为什么要设计双亲委派模型,有什么好处?
1、确保安全,避免 Java 核心类库被修改
2、避免重复加载
3、保证类的唯一性
具体就是因为所有的加载都是从上层类加载器开始加载,如果上层加载过同名的类时,就不再次加载了
如何打破双亲委派模型
想要打破这种模型,那么就自定义一个类加载器,重写其中的loadClass 方法,使其不进行双亲委派即可;
Class.forName()与重写loadClass方法的区别是,Class.forName()加载后会对类进行初始化,而loadClass不会对类进行初始化;
监控类的加载参数
-XX:+TraceClassLoading 监控美的加载
Tomcat打破双亲委派机制
Tomcat 新增了 3 个基础类加载器和每个Web 应用的类加载器+JSP 类加载器;
注意,在tomcat5.5版本后Catalina类加载器和Share类加载器加载的内容都交给了common类加载器;
以为tomacat下面可以部署多个项目,所以WebApp类加载器和Jsp类加载器是多个的,一个项目一个WebApp类加载器,而jsp类加载器是每个jsp页面一个jsp类加载器
为什么 Tomcat 要破坏双亲委派模型?
Tomcat 是 web 容器,那么一个 web 容器可能需要部署多个应用程序:
1、部署在同一个 Tomcat 上的两个 Web 应用所使用的Java 类库要相互隔离
(比如同一tomcat下使用的spring类库版本是不同的,所以每个项目都要使用自己的类库,然后使用项目自己的appClassLoaderj加载)
2、部署在同一个 Tomcat 上的两个 Web 应用所使用的 Java 类库要互相共享;
(比如两个项目都是用到了jdbc的jar包,所以只需要加载一次就可以,然后就可以把jar包放到lib目录下使用tomcat的类加载器加载一次class文件到原空间就可以了)
3、保证 Tomcat 服务器自身的安全不受部署的 Web 应用程序影响
4、需要支持JSP 页面的热部署和热加载
热加载和热部署
热加载 是指可以在不重启服务的情况下让更改的代码生效,热加载可以显著的提升开发以及调试的效率,它是基于Java 的类加载器实现的,但是由于热加载的不安全性,一般不会用于正式的生产环境;
热部署 是指可以在不重启服务的情况下重新部署整个项目,比如 Tomcat 热部署就是在程序运行时,如果我们修改了 War 包中的内容,那么 Tomcat 就会删除之前的 War 包解压的文件夹,重新解压新的 War 包生成新的文件夹
如何实现热加载
在程序代码更改且重新编译后,让运行的进程可以实时获取到新编译后的 class 文件,然后重新进行加载;
1、实现自己的类加载器
2、从自己的类加载器中加载要热加载的类
3、不断轮训要热加载的类 class 文件是否有更新,如果有更新,重新加载(可以定义一个定时任务)
java程序是如何运行起来的
1、Mall.java -->javac --> Mall.class --> java Mall (jvm 进程,也就是一个jvm 虚拟机)(main方法启动)
2、Mall.java -->javac–> Mall.class --> Mall.jar --> java -jar Mall.jar
3、Mall.java --> javac --> Mall.class -->Mall.war --> Tomcat --> startup.sh -->org.apache.catalina.startup.Bootstrap (jvm 进程,也就是一个jvm 虚拟机)其实运行起来一个Java 程序,都是通过 D:devJava dk1.8.0 251\binava 启动一个JVM虚拟机,在虚拟机里面运行 Mall.class 字节码文件;
都是由一个包含main方法的类来启动,然后调用其他类的方法使整个程序运行起来