java-类加载机制

1.jdk安装时会有两套jre的原因
jdk的各种工具都是由java编写的 这些工具本身也需要运行环境 所以jdk下会自带一套jre
jdk的bin目录下的各种exe程序都很小 只是一个包装器 内部实质都是调用的jar(\lib\tools.jar)文件来实现的

2.多套jre时实际选用哪套jre
1)自己目录下的jre 2)父目录下的jre 3)windows注册表下的jre

3.类文件的载入
   java.exe
 > 找到jre(将类文件.class移交给jre的类装载器)
 > 类装载器载入基础类库(\lib\rt.jar)

4.预先载入与按需载入
一般基础类库会预先载入 应用的类会按需载入 即在实际调用时才载入
有时我们会需要显式的预先载入一些类
1)java.lang.Class.forName()
forName(String className) 
forName(String name, boolean initialize, ClassLoader loader)
initialize会指定是否执行static方法 默认true false的话会在实际new的时候在执行
loader是指定类加载器 默认使用调用者的类加载器
2)java.lang.ClassLoader.loadClass()
loadClass 与 initialize false时一样 不会执行static方法
3)URLClassLoader

5.类加载机制
BootstrapLoader > ExtClassLoader 设置parent为null
BootstrapLoader > AppClassLoader(System Loader) 设置parent为ExtClassLoader

BootstrapLoader的搜索路径为 系统参数sun.boot.class.path
除了加载rt.jar等 默认还会搜索jre/classes

ExtClassLoader的搜索路径为 系统参数java.ext.dirs
默认为 jre的\lib\ext

AppClassLoader的搜索路径为 系统参数java.class.path
顺序为 缺省情况下为"."(当前目录) -classpath(-cp)指定的路径 classpath指定的系统变量

6.双亲委托模型
1.类加载时会先请示其parent使用对应的搜索路径搜索 找不到的话则按照自己的搜索路径搜索
2.同一个加载器:类A引用到类B,则由类A的加载器去加载类B,保证引用到的类由同一个加载器加载。
3.实际应用中,经常需要破坏双亲委托 常用的方法是Thread.getContextClassLoader()方法
eg1: jre/classes/father.java 
     jre/classes/son.java 
     jre/lib/ext/father.java 
     则jre/classes/father.java和jre/classes/son.java会被加载
eg1: jre/classes/father.java 
     jre/lib/son.java 
     jre/lib/ext/father.java 
     则jre/classes/father.java会被加载 但jre/classes/son.java会NoClassFoundException
     因为jre/classes/father.java会被BootstrapLoader加载 BootstrapLoader不会搜索子加载器的路径 只会搜索自己的路径

7.虚拟机对类的加载
7-1.生命周期 加载 连接(验证 准备 解析) 初始化 使用 卸载
7-2.类初始化的场景
虚拟机规范定义了 有且只有 的四种定义
a.遇到new getstatic setstatic invokestatic这4条字节码指令时
b.当初始化子类时父类还未初始化 初始化父类(但对于接口 子接口初始化时并不会初始化父接口)
c.对类进行反射调用时 如果类未初始化 则会执行初始化
d.用户指定的带main方法的主类

场景1:
class A{
    static{
        System.out.println("A init");
    }
    public static val = 100;
}
class B extends A{
    static{
        System.out.println("B init");
    }
}
输出:A init
调用B.val并不会初始化子类 但会初始化父类 只有直接定义的类才会初始化

场景2:
B[] arrB = new B[10] 不会初始化B 实际上虚拟机会通过newarray字节码命令生成B的数组类型

场景3:
class A{
    static{
        System.out.println("A init");
    }
    public final static val = 100;
}

此时不会初始化A 因为final static会被编译到实际调用类的常量池 A与实际调用类在编译后就没有关系了

7-3.类的加载过程
1)加载
  a.加载二进制流信息
  b.将二进制的静态存储信息转化为方法区中的运行时数据结构
  c.在java堆中生成Class对象 作为方法区数据的访问入口
2)验证
3)准备
  a.正式为static变量分配内存 设置变量初始值
    public static int value = 123;
    这里的初始值并不是123 而是0 实际的123赋值操作会被分解到类构造器<clinit>方法中 
    public static final int value = 123;
    这里的初始值并是123 常量的值会在准备阶段就设置好
4)解析
将符号引用替换为直接引用
5)初始化
a.即执行<clinit>的过程
  clinit中的方法是由编译器按照源码中出现的顺序对static变量和方法块收集而成
  因此 静态语句可以正常访问和赋值之前定义的语句 但只能赋值之后出现的静态变量 不能访问之后出现的静态变量(在编译期就会报错的)
  场景1:编译正确
      private static int a =1;
      private static int b =a;
     --准备
      a=0;
      b=0;
     --初始化
      a=1;
      b=a;
  场景2:编译错误
      private static int b =a;//不能访问
      private static int a =1;
  场景3:编译正确
      static{
          a=2;//可以赋值
      }
      private static int a =1;
      --准备
      a=0;
      --初始化
      a=2;
      a=1;
b.虚拟机会保证父类的<clinit>的过程在子类之前已经执行完成
  因此父类的静态方法会优先于子类执行
  但是 接口中的<clinit>不需要先执行父类的<clinit>
c.如果类中没有静态语句块 也没有变量赋值操作则不会有cinit操作
d.虚拟机会保证<clinit>的阻塞执行 如果同时有多个线程去初始化一个类
则其他线程都要阻塞 <clinit>中的耗时操作会造成严重阻塞

6)类的相等比较(equals instanceof等)
只有在类是由同一个类加载器加载的时候 类的相等比较才有意义
不同类加载器加载的同一个类 instanceof并不相等

转载于:https://my.oschina.net/dajianguo/blog/1417603

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值