一个类在使用前,如何经过类调用静态字段,静态方法,或者new一个实例对象,第一步就是须要类加载,而后是链接和初始化,最后才能使用。java
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialzation)、使用(Using)和卸载(Unloading)7 个阶段。其中验证、准备、解析 3 个部分统称为链接(Linking),这 7 个阶段的发生顺序以下图所示:web
加载、验证、准备、初始化和卸载这 5 个阶段的顺序是肯定的,类的加载过程必须按照这种顺序循序渐进地开始,而解析阶段则不必定:它在某些状况下能够在初始化阶段以后再开始,这是为了支持 Java 语言的运行时绑定(也称为动态绑定或晚期绑定)。注意,这里笔者写的是循序渐进地 “开始”,而不是循序渐进地 “进行” 或 “完成”,强调这点是由于这些阶段一般都是互相交叉地混合式进行的,一般会在一个阶段执行的过程当中调用、激活另一个阶段。面试
什么状况下须要开始类加载过程的第一个阶段:加载?Java 虚拟机规范中并无进行强制约束,这点能够交给虚拟机的具体实现来自由把握。可是对于初始化阶段,虚拟机规范则是严格规定了有且只有 5 种状况必须当即对类进行 “初始化”(而加载、验证、准备天然须要在此以前开始):shell
对于这 5 种会触发类进行初始化的场景,虚拟机规范中使用了一个很强烈的限定语:“有且只有”,这 5 种场景中的行为称为对一个类进行主动引用。除此以外,全部引用类的方式都不会触发初始化,称为被动引用。apache
类加载器就是将 .java 代码文件编译成 .class 字节码文件后,Java虚拟机的类加载器经过读取此类的二进制流,转换成目标类的实例。安全
除了Java会生成字节码外,运行在JVM上的JRuby,Scala,Groovy一样须要编译成对应的 .class 文件,这里列举了四种不一样的字节码,不单是Java才生成字节码文件。多线程
经常使用的类加载器有4种:app
1.Bootstrap ClassLoader:启动类加载器,加载JAVA_HOME/lib
目录下的类。以下图选中的就是框架
2.ExtClassLoader:扩张类加载器,加载JAVA_HOME/lib/ext
目录下的类。eclipse
3.AppClassLoader:应用程序类加载器,加载用户指定的classpath(存放 src 目录 Java 文件编译以后的 class 文件和 xml、properties 等资源配置文件的 src/main/webapp/WEB-INF/classes 目录)下的类
4.UserClassLoader:用户自定义的类加载器(只要继承 ClassLoader并实现 findClass(String name) 方法),自定义加载路径。
类加载时并不须要等到某个类被首次主动使用时再加载它,JVM类加载器会在预料某个类要使用时预先加载。双亲委派模型,以下图:
Java 类加载基于双亲委派模型——当有类加载请求时,从下往上检查类是否被加载,若是没被加载,UserClassLoader 就委托父类 AppClassLoader 加载,AppClassLoader 继续委托其父类 ExtClassLoader 加载,接着分派给 Bootstrap ClasssLoader 加载;
若是没法加载就返回到发起加载请求的类加载一直到由最开始发起加载请求的 UserClassLoader 加载,全部类最终都会去到顶层。Bootstrap ClasssLoader 开始加载,没法加载就返回子加载器处理,一直到最开始的加载器。
这样子,就算用户自定义了 java.lang.Object 类和系统的 java.lang.Object 类重复,也不会被加载,下面咱们就来自定义本身的类加载器。
/** * Created by cong on 2018/8/2. */ public class MyClassLoader extends ClassLoader { public MyClassLoader() { super(); } public MyClassLoader(ClassLoader parent) { super(parent); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { // do something // 本身先不加载,先让父类加载 return super.findClass(name); } public static void main(String[] args) throws ClassNotFoundException { MyClassLoader myLoader = new MyClassLoader(); // 打印当前类路径 System.out.println(System.getProperty("java.class.path")); // ClassPath路径下并不存在Demo.class类,故抛出异常 System.out.println(myLoader.loadClass("Demo").getClassLoader().getClass().getName()); } }
运行结果以下:
学习自定义类加载器后,咱们看下源码里双亲委派模型是怎么加载类的。源码以下:
public abstract class ClassLoader { private final ClassLoader parent; protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 先检查类是否已经被加载 Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { //若是存在父类加载器,就委托给父类加载器加载 if (parent != null) { c = parent.loadClass(name, false); } else { //若是不存在父类加载器,就委托给顶层的启动类加载器加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException异常被抛出则代表父类加载器加载失败 } if (c == null) { // 若是父类没法加载,就本身加载 long t1 = System.nanoTime(); c = findClass(name); sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } } }
咱们看到上面 loadClass 类里有同步代码块 synchronized (getClassLoadingLock(name)),而在 JDK1.6 以前是方法 protected synchronized Class<?> loadClass(String name, boolean resolve)
上锁,锁住方法当前对象。
这就致使一个问题,当 A 包依赖 B 包,A 在本身的类加载器的 loadClass 方法中,最终调用到 B 的类加载器的 loadClass 方法。A 先锁住本身的类加载器,而后去申请 B 的类加载器的锁,当 B 也依赖 A 包时,B 加载 A 的包时,过程相反,在多线程下,就容易产生死锁。若是类加载器是单线程运行就会安全,但效率会很低 同步代码块 synchronized (getClassLoadingLock(name)) 锁住的是一个特定对象。
private final ConcurrentHashMap<String, Object> parallelLockMap; protected Object getClassLoadingLock(String className) { Object lock = this; // parallelLockMap是一个ConcurrentHashMap if (parallelLockMap != null) { // 锁对象 Object newLock = new Object(); // putIfAbsent(K, V)方法查看K(className)和V(newLock)是否相互对应, // 是的就返回V(newLock),不然返回null // 每一个className关联一个锁,并将这个锁返回,缩小了锁定粒度了,只要类名不一样,就会匹配不一样的锁, // 就是并行加载,相似ConcurrentHashMap里面的分段锁, // 不锁住整个Map,而是锁住一个Segment,每次只须要对Segment上锁或解锁,以空间换时间 lock = parallelLockMap.putIfAbsent(className, newLock); if (lock == null) { // 建立一个新锁对象 lock = newLock; } } return lock;
经过并行加载,能够提高加载效率,而后讲下类加载的面试题,在 Java 反射中 Class.forName() 加载类和使用 ClassLoader 加载类是不同的。例子以下:
/** * Created by cong on 2018/8/5. */ public class MyCase { static { System.out.println("执行了静态代码块"); } private static String field = methodCheck(); public static String methodCheck() { System.out.println("执行了静态代方法"); return "给静态变量赋值"; } }
----------------------------------------------------
/** * Created by cong on 2018/8/5. */ public class DemoTest { public static void main(String[] args) { try { System.out.println("Class.forName开始执行:"); //hjc是包名 Class.forName("hjc.MyCase"); System.out.println("ClassLoader开始执行:"); ClassLoader.getSystemClassLoader().loadClass("hjc.MyCase"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
运行结果以下:
Class.forName 是加载 MyCase 类并完成初始化,给静态代码块和静态变量赋值,而 ClassLoader 只是将类加载进 JVM 虚拟机,并无初始化。
接下来咱们进入Class.forName的源码探究,源码以下:
@CallerSensitive public static Class<?> forName(String className) throws ClassNotFoundException { return forName0(className, true, ClassLoader.getClassLoader(Reflection.getCallerClass())); }
Class.forName 底层也是调用了 ClassLoader,只是第二个参数为 true,即加载类并初始化,默认就会初始化类,JDBC 链接就是用 Class.forName 加载驱动。因此注册链接驱动会在静态代码块执行,Sprng 里的 IOC 是经过 ClassLoader 来产生,能够控制 Bean 的延迟加载(首次使用才建立)。
为了实现代码热替换,模块化和动态化,就像鼠标同样即插即用,双亲委派这种树状的加载器就难以胜任,因而出现了 OSGI 加载模型,OSGI 里每一个程序模块(Bundle,就是普通的 jar 包, 只是加入了特殊的头信息,是最小的部署模块)都会有本身的类加载器,当须要更换程序时,就连同 Bundle 和类加载器一块儿替换,是一种网状的加载模型,Bundle 间互相委托加载,并非层次化的。
Java 类加载机制的隔离是经过不一样类加载器加载指定目录来实现的,类加载的共享机制是经过双亲委派模型来实现,而 OSGI 实现隔离靠的是每一个 Bundle 都自带一个独立的类加载器 ClassLoader。
OSGI 加载 Bundle 模块的顺序
若是用 Java 的结构的项目去部署,当项目复杂度提高时,每次上线,代码只是增长或者修改了部分功能,但都得关掉服务,从新部署全部的代码和配置,管理沟通成本都很高,很容产生线上事故,而 OSGI 的应用是一个模块化的系统,避免了部署时 jar 或 classpath 错综复杂依赖管理,发布应用和更新应用都很强大,能够热替换特定的 Bundle 模块,提升部署可靠性。
接下来咱们用IDE建立一个OSGI应用,首先要去 http://download.eclipse.org/equinox/ 下载最新的OSGI 框架enquinox
建立一个 OSGI 应用。打开 Eclipse,File->New->Project:
选择 OSGI 框架 Equniox(Eclipse 强大的插件机制就是构建于 OSGI Bundle 之上,Eclipse 自己就包含了 Equniox) :
接下来,勾选建立 Activator 类,新建一个创Activator 类,每一个 Bundle 启动时都会调用 Bundle(模块)里 Activator(类)的 start 方法,中止时调用 stop 方法,代码以下:
import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; /** * Created by cong on 2018/8/5. */ public class Activator implements BundleActivator { private static BundleContext context; static BundleContext getContext() { return context; } public void start(BundleContext bundleContext) throws Exception { Activator.context = bundleContext; //添加输出This is OSGI Projcect System.out.println("This is OSGI Projcect"); } public void stop(BundleContext bundleContext) throws Exception { Activator.context = null; } }
接下来进行一下配置,Run->Run Configuration-> 双击 OSGI Framework 生成项目配置:以下图:
而后点击运行按钮,能够看到控制台输出 This is OSGI Projcect。在控制台咱们输入 ss ( short status) 查看服务状态:
This is OSGI Projcect osgi> ss "Framework is launched." id State Bundle 0 ACTIVE org.eclipse.osgi_3.12.100.v20180210-1608 1 ACTIVE org.apache.felix.gogo.runtime_0.10.0.v201209301036 2 ACTIVE org.apache.felix.gogo.command_0.10.0.v201209301215 // ACTIVE代表 com.osgi.bundle.demo Bundle运行中 3 ACTIVE com.osgi.bundle.demo_1.0.0.qualifier 4 ACTIVE org.apache.felix.gogo.shell_0.10.0.v201212101605 5 ACTIVE org.eclipse.equinox.console_1.1.300.v20170512-2111 // 中止 com.osgi.bundle.demo Bundle osgi> stop com.osgi.bundle.demo osgi> ss "Framework is launched." id State Bundle 0 ACTIVE org.eclipse.osgi_3.12.100.v20180210-1608 1 ACTIVE org.apache.felix.gogo.runtime_0.10.0.v201209301036 2 ACTIVE org.apache.felix.gogo.command_0.10.0.v201209301215 // RESOLVED 代表 Bundle com.osgi.bundle.demo 中止了 3 RESOLVED com.osgi.bundle.demo_1.0.0.qualifier 4 ACTIVE org.apache.felix.gogo.shell_0.10.0.v201212101605 5 ACTIVE org.eclipse.equinox.console_1.1.300.v20170512-2111 // 经过close关闭整个应用框架 osgi> close Really want to stop Equinox? (y/n; default=y) y osgi>
一个 Bundle 包含 MANIFEST.MF,也就是 Bundle 的头信息,Java 代码以及配置文件(XML,Properties),其中 MANIFEST.MF 包含了下面的信息。以下所示:
/*版本号*/ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 /*名字*/ Bundle-Name: Demo Bundle-SymbolicName: com.osgi.bundle.demo Bundle-Version: 1.0.0.qualifier /*Bundle类*/ Bundle-Activator: com.osgi.bundle.demo.Activator Bundle-Vendor: OSGI /*依赖环境*/ Bundle-RequiredExecutionEnvironment: JavaSE-1.7 /*导入的包*/ Import-Package: org.osgi.framework;version="1.3.0" Bundle-ActivationPolicy: lazy
1.控制框架
1.launch 启动框架
2.shutdown 中止框架
3.close 关闭、退出框架
4.exit 当即退出,至关于 System.exit
5.init 卸载全部 bundle(前提是已经 shutdown)
6.setprop 设置属性,在运行时进行
2.控制 Bundle
1.Install 安装 uninstall 卸载
2.Stop 中止
3.Refresh 刷新
4.Update 更新
3.展现状态
1.Status 展现安装的 bundle 和注册的服务
2.Ss 展现全部 bundle 的简单状态
3.Services 展现注册服务的详细信息
4.Packages 展现导入、导出包的状态
5.Bundles 展现全部已经安装的 bundles 的状态
6.Headers 展现 bundles 的头信息,即 MANIFEST.MF 中的内容
7.Log 展现 LOG 入口信息
4.其余
Exec 在另一个进程中执行一个命令(阻塞状态)
1.Fork 和 EXEC 不一样的是不会引发阻塞
2.Gc 促使垃圾回收
3.Getprop 获得属性,或者某个属性
5.控制启动级别
1.Sl 获得某个 bundle 或者整个框架的 start level 信息
2.Setfwsl 设置框架的 start level
3.Setbsl 设置 bundle 的 start level
4.setibsl 设置初始化 bundle 的 start level