JVM Classloader



初始化:

虚拟机对类的何时加载没有约束,但对类的初始化约定了5个条件(加载,验证,准备需在其之前开始);
  • new,读写非final(final的已被放入常量池)的static字段,调用静态方法;
  • java.lang.reflect
  • 初始化一个类时,其父类未初始化,初始化其父类;
  • 程序入口(main())所在的类;
  • Jdk1.7动态语言的支持,对java.lang.invoke.MethodInvokeHandle的解析是REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,这个句柄对应的类没有被初始化,则初始化该类;

被动使用不会导致类的初始化:
  • Sub extends Super,Super有static field value,main中调用Sub.Value,只会导致Super的初始化;
  • 通过数组定义来引用类,不会导致类的初始化(jvm会自动生成一个类,提供数组的length和clone方法(可见的));
  • 引用类的static final field不会导致初始化;

接口的初始化:
  • 接口不能包含static{},子接口的初始化不会触发父接口的初始化;

初始化阶段是执行类构造器方法的过程;
  • 类构造器方法:<clinit>()方法是编译器自动收集类中的索引类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的(按代码顺序);静态语句块只能访问定义在其之前的变量,但是能够对定义在之后的变量进行赋值;
  • <clinit>()与类构造函数不同,不需要显示调用父类构造器,jvm会保证在执行子类的<clinit>之前父类的<clinit>已经执行完毕;
  • <clinit>不是必须的,jvm可以不生成<clinit>;
  • 接口中不能使用static{},但仍有变量初始化,所以接口也会有<clinit>,但是接口<clinit>执行之前不需要执行父接口的<clinit>,只有当父接口的变量被使用时,父接口的<clinit>才会被执行,接口的实现类也一样;
  • 虚拟机会保证类的<clinit>方法在多线程环境下被正确加锁,可能会导致线程阻塞,在实际情况下这种阻塞很隐蔽;



类加载器:

ClassLoader的重要方法:
  • public Class<?> loadClass(String name) throws ClassNotFoundException
    • 载入并返回一个Class
  • protected final Class<?> defineClass(byte[] b, intoff, intlen)
    • 定义一个类,不公开调用
  • protected Class<?> findClass(String name) throws ClassNotFoundException
    • loadClass回调该方法,自定义ClassLoader的推荐做法
  • protected final Class<?> findLoadedClass(String name)
    • 寻找已经加载的类

双亲委派模型:


  • Bootstrap classLoader:只加载<java_home>/lib下的jar包或-Xbootclasspath参数指定的目录,无法被java程序获得,使用null表示;
  • Extension classLoader:由sun.misc.Launcher$ExtClassLoader实现,加载<java_home>/lib/ext下的jar包或java.ext.dirs指定路径下的jar包;
  • Application classLoader:由sun.misc.Launcher$AppClassLoader实现,加载classPath下的jar包,ClassLoader.getSystemClassLoader()的返回值就是它,如果不适用自定义加载器,默认使用该加载器加载应用程序;

破坏双亲委派模型:
  • Jdk版本兼容问题,用户应当实现ClassLoader的findClass方法,而不要去overwrite loadClass();
  • 基础类对实现类有依赖,典型的:JNDI,JDBC,JCE,JAXB,JBI,实现的方式:线程上下文加载器(Thread Context loader),Thread拥有一个setContextClassloader(ClassLoader cl),直接从父类继承,如果没有一个全局的设置,则默认是系统加载器,这样的话,JNDI服务就可以使用线程上下文加载器加载SPI代码;
  • 对程序的动态性追求,OSGI,实现代码热替换(hot swap)和热部署(hot deployment),OSGI实现模块化热部署的关键在于自定义的类加载机制的实现,每一个程序模块(OSGI中成为bundle)都有一个自己的类加载器,当需要更换一个bundle,将bundle和类加载器一起替换以实现代码的热替换;
  • OSGI中,ClassLoader不再是双亲结构,而是更复杂的网状结构;
  • 通过反射强制指定类加载器加载指定 Class:
    • public static void main(String args[]) throws Exception {
        ClassLoadercl=FindClassOrder2.class.getClassLoader();
        byte[] bHelloLoader=loadClassBytes("geym.jvm.ch6.findorder.HelloLoader");
        Method md_defineClass=ClassLoader.class.getDeclaredMethod("defineClass", byte[].class,int.class,int.class);
        md_defineClass.setAccessible(true);
        md_defineClass.invoke(cl, bHelloLoader,0,bHelloLoader.length);
        md_defineClass.setAccessible(false);

        HelloLoaderloader = new HelloLoader();
        System.out.println(loader.getClass().getClassLoader());
        loader.print();
      }
  • Classloader SPI(service provider interface)
    • Thread. setContextClassLoader()
    • 用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题
    • 基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例
    • // 代码来自于 javax.xml.parsers.FactoryFinder 展示如何在启动类加载器加载AppLoader的类
      // 上下文ClassLoader可以突破双亲模式的局限性

      static private Class getProviderClass(String className, ClassLoader cl,
              boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException
      {
          try {
              if (cl == null) {
                  if (useBSClsLoader) {
                      return Class.forName(className, true, FactoryFinder.class.getClassLoader());
                  } else {
                      cl = ss.getContextClassLoader();
                      if (cl == null) {
                          throw new ClassNotFoundException();
                      }
                      else {
                          return cl.loadClass(className); //使用上下文ClassLoader
                      }
                  }
              }
              else {
                  return cl.loadClass(className);
              }
          }
          catch (ClassNotFoundException e1) {
              if (doFallback) {
                  // Use current class loader - should always be bootstrap CL
                  return Class.forName(className, true, FactoryFinder.class.getClassLoader());
              }
      …..


tomcat 正统的类加载器例子:


  • 主流的web服务器都实现了自定义的ClassLoader(不止一个),解决如下问题:
    • 实现不同应有的library的隔离(相同类库的版本不一样)
    • 实现类库共享,减少内存占用(防止方法区过度膨胀)
    • Server尽可能保证自身安全不受部署的web application的影响(许多server也是使用java实现)
    • 支持JSP的server大多要支持hotSwap功能;
  • Tomcat中有3组存放lib的目录:common/,server/,shared/,和application自身的WEB-INFO/:
    • Common/:被所有application和tomcat server公用
    • Server/:lib仅可以被tomcat server使用
    • Shared/:可以被所有application公用,但是tomcat server不可见
    • WEB-INFO/:仅能被此web application使用
  • 使用双亲委派模式实现
    • WebAPPClassLoader和JasperLoader通常有多个实例,每个web application对应一个WebAPPClassLoader instance,每一个jsp文件对应一个JasperLoader instance,
      • JasperLoader instance出现的目的是为了被丢弃,当tomcat发现jsp文件被修改,通过建立一个新的JasperLoader instance来替代旧的JasperLoader instance实现HotSwap


















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值