白话说java类加载过程
java中万物皆对象,而对象的来源则是类class。从写出来的代码,到运行过程的作用,都是怎么完成的呢?
且让我们一步步看来。
当我们写完java代码后,第一步,编译打包,这个过程就会将java代码编译为class字节码,然后就可以到处在java环境中运行了。
java应用是运行于jvm之上的的,那么jvm又是怎么把class文件加载运行起来的呢?
有两个方向可以讲,一是直观的加载感受,二是实际的加载过程。
直观的加载方式是,jvm一开始就会去执行一个指定的main函数,这个main函数内又会有其他许多依赖,比如父类,组合,然后jvm会先去把这些类加载进来,递归完成后,就可以运行程序逻辑了。从这个角度来说,java其实也是函数式编程的一种。
上面的加载过程不能说错,但是确实是太门外汉的感觉了。类加载过程分为7个步骤:加载(找到二进制类)、验证(格式,元数据,字节码,符号引用等验证)、准备(变量赋初值系统级零值)、解析(符号引用转直接引用,类接口字段方法引用解析)、初始化(类变量初始化,可控性强,安全使用<clinit>方法聚合)、使用、卸载;
类加载过程中使用了一个重要组件,类加载器,它是在类加载阶段中,通过一个全局限定名来获取类的一个工具,它是java技术中很重要的一项技术。
类加载器,主要分为三类,bootstrap(lib目录,c++编写),extension(lib/ext目录), application(classpath路径) 以及类似的 user classloader,很多用户自定义的类加载器都统称为user classloader。
类加载器起到加载类找到类的作用,每个类加载器都有一个独立的命名空间,两个类要相等,必须满足两个条件,同一个类加载器中和同一个类。
类加载器的实现机制主要是双亲委派模型,它是java设计者推荐的一种类加载实现方式。它的工作过程是,当一个类加载器收到一个类加载请求时,它不会自己尝试去加载,而是把这个请求委派给父类加载器去加载,每层类加载都是如此,最终所有加载请求都会传到启动类加载器,只有当父类反馈自己无法完成加载请求时,子类才会尝试自己加载。即父类加载器优先原则。如果不使用这个模型的话,java的基石将被动摇,因为每个用户都可以编写所有类,也可以自己写加载器去加载它,从而不使用java提供的东西,将导致一片混乱。(当然,这些说的是用户的类加载器不加以区分地加载任何类导致,否则不会存在问题)
实现类加载只需继承ClassLoader, 并覆盖findClass()即可,完整处理覆盖loadclass()即可。
双亲委派模型类加载器被破坏,1. 早期类加载器;2. 类jndi服务,它是java标准服务,由各厂商实现,通过spi机制发现,即优先级低于java系统级,所以必须反过来加载这些类。其实现方式是使用ThreadContextClassLoader, jndi使用这个类加载器去加载所需spi代码,即父类请求子类去加载去加载。为什么不能用子类加载器直接加载,因为子类根本是不可见的,请求加载时不是请求它。如果没有jndi,我们实现类加载将会是这样,Class.forName("com.mysql.jdbc.Driver", true, Thread.currentThread().getContextClassLoader());然后设置地址,密码等等。 而使用jndi之后,就简单了。DataSource ds = (DataSource) cntxt.lookup("jdbc/mysql-test"); 就可以了。
常用的jndi服务有:DNS、XNam 、Novell目录服务、LDAP、 CORBA对象服务、文件系统、RMI、DSML v1&v2。
3. osgi。为了追求动态性,热部署等,而产生生的运动。其实现原理是每一个模块都有自己的类加载器,当需要更换一个bundle时,就把bundle连同类加载器一起换掉以实现热替换。
ogsi不再遵从双亲委派模型,而是复杂的网状结构。但其实它对java基类仍然遵从前面设计,java.*开头委托父类加载。否则委托名单内的类委托给父类。否则委派给export这个类的bundle类加载器。否则当前bundle的classpath使用自己的类加载器。否则查找是否在当前fragment bundle中,在则委托。否则dynamic import列表bundle,委派给对应的类加载器。否则失败。 大部分是平级加载。弄懂了osgi实现,就可以算是掌握了类加载器的精髓。