JVM类加载机制

类加载机制就是JVM把class文件加载到内存里面,并对数据进行校验、准备、解析和初始化,最终形成能被JVM直接使用的java类型的过程。

加载:主要做三件事

1)通过类的全限定名获取该类的二进制字节流。
2)将字节流文件代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

连接

验证:验证是连接的第一步,主要确保加载进来的字节流符合JVM规范。主要有四个检验动作

1)文件格式验证
2)源数据验证(是否符合java语言规范)
3)字节码验证(确定程序语义合法,符合逻辑)
4)符号引用验证(确保下一步的解析能正常执行)

准备:准备是连接的第二步,主要为静态变量在方法区分配内存,并设置默认初始值。

解析:解析是连接阶段的第三步,是虚拟机将常量池内的符号引用转换为直接引用的过程。

初始化:初始化阶段是类加载过程的最后一步,主要是根据程序中的赋值语句主动为类变量赋值。

注意:如果有父类并且父类为初始化的时候,先去初始化父类,然后在执行子类初始化语句。

什么时候对类进行初始化?
1)使用new关键字创建该类实例化对象的时候。
2)读取或设置类静态字段的时候(在编译时就被放入常量池的静态字段除外)
3)调用类静态方法的时候
4)使用反射Class.forName()对类进行反射调用的时候,该类需要初始化。
5)初始化一个类的时候,有父类,先初始化父类(接口除外,接口只在调用的时候才会被初始化)
6)被标明为启动类的类,既包含main()方法的类要初始化。
7)当使用JDK1.7的动态语言支持时,如果一个java.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

二、类加载器:类加载器实现的功能就是加载阶段获取二进制字节流的时候。
JVM听了三种系统的类加载器。
1)启动类加载器(Bootstrap ClassLoader):最顶层的类加载器,负责加载JAVA_HOME\bin目录中的,或者-Xbootclasspath参数指定路劲中的,且被虚拟机认可的类。
2)扩展类加载器(Extension ClassLoader):负责加载JAVA_HOME\lib\ext目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
3)应用程序类加载器(Application ClassLoader):也叫做系统类加载器,可以通过getSystemClassLoader()获取,负责加载用户路劲(classpath)上的类库。如果没有自定义加载器,一般这个就是默认的类加载器。

类加载器之间的层次关系如下:

类加载器之间的这种层次关系叫做双亲委派模型。
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。
这里的类加载器之间的父子关系一般不是以继承关系实现的。

双亲委派模型的工作过程
如果一个类接收到类加载请求,他自己不会去加载这个请求,而是将这个类加载请求委派给父类加载器,这样一层一层传送,直到到达启动类加载器。
只有当父类加载器无法加载这个请求时,子加载器才会尝试自己去加载。

双亲委派模型的代码实现:
双亲委派模型的代码实现集中在java.lang.ClassLoader的loadClass()方法中。
1)首先检查类是否被加载,没有则调用父类加载器的loadClass()方法。
2)若父类加载器为空,则默认使用启动类加载器作为父加载器。
3)若父类加载失败,抛出ClassNotFoundExcepton异常后,再调用自己的findClass()方法。

代码如下:
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
//1 首先检查类是否被加载
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
//2 没有则调用父类加载器的loadClass()方法;
c = parent.loadClass(name, false);
} else {
//3 若父类加载器为空,则默认使用启动类加载器作为父加载器;
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
//4 若父类加载失败,抛出ClassNotFoundException 异常后
c = findClass(name);
}
}
if (resolve) {
//5 再调用自己的findClass() 方法。
resolveClass©;
}
return c;
}
破坏双亲委派模型:
双亲委派模型很好的解决了各个类加载器加载基础类的统一性问题。即越基础的类由越上层的加载器 进行加载。
若加载的基础类中需要回调用户代码,而这时顶层的类加载器无法识别这些用户代码,怎么办呢?这 时就需要破坏双亲委派模型了。

破坏的过程:
1、JNDI破坏双亲委派模型
JNDI是Java标准服务,它的代码由启动类加载器去加载。但是JNDI需要回调独立厂商实现的代 码,而类加载器无法识别这些回调代码(SPI)。
为了解决这个问题,引入了一个线程上下文类加载器。 可通过Thread.setContextClassLoader() 设置。
利用线程上下文类加载器去加载所需要的SPI代码,即父类加载器请求子类加载器去完成类加载的 过程,而破坏了双亲委派模型。

    2、Spring破坏双亲委派模型 
            Spring要对用户程序进行组织和管理,而用户程序一般放在WEB-INF目录下,由                                         WebAppClassLoader类加载器加载,而Spring由Common类加载器或Shared类加载器加载。 
            那么Spring是如何访问WEB-INF下的用户程序呢? 
            使用线程上下文类加载器。 Spring加载类所用的classLoader都是通过                                                         Thread.currentThread().getContextClassLoader()获取的。当线程创建时会默认创建一个                           AppClassLoader类加载器(对应Tomcat中的WebAppclassLoader类加载器):                                           setContextClassLoader(AppClassLoader)。 
            利用这个来加载用户程序。即任何一个线程都可通过getContextClassLoader()获取到                                   WebAppclassLoader。 

class Singleton{
private static Singleton singleton = new Singleton();
public static int value1;
public static int value2 = 0;

private Singleton(){
value1++;
value2++;
}

public static Singleton getInstance(){
return singleton;
}

}

class Singleton2{
public static int value1;
public static int value2 = 0;
private static Singleton2 singleton2 = new Singleton2();

private Singleton2(){
value1++;
value2++;
}

public static Singleton2 getInstance2(){
return singleton2;
}

}

public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println(“Singleton1 value1:” + singleton.value1);
System.out.println(“Singleton1 value2:” + singleton.value2);

Singleton2 singleton2 = Singleton2.getInstance2();
System.out.println(“Singleton2 value1:” + singleton2.value1);
System.out.println(“Singleton2 value2:” + singleton2.value2);
}
类加载机制就是JVM把class文件加载到内存里面,并对数据进行校验、准备、解析和初始化,最终形成能被JVM直接使用的java类型的过程。

加载:主要做三件事

1)通过类的全限定名获取该类的二进制字节流。
    2)将字节流文件代表的静态存储结构转化为方法区的运行时数据结构。
    3)在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

连接

验证:验证是连接的第一步,主要确保加载进来的字节流符合JVM规范。主要有四个检验动作

1)文件格式验证
    2)源数据验证(是否符合java语言规范)
    3)字节码验证(确定程序语义合法,符合逻辑)
    4)符号引用验证(确保下一步的解析能正常执行)
准备:准备是连接的第二步,主要为静态变量在方法区分配内存,并设置默认初始值。

解析:解析是连接阶段的第三步,是虚拟机将常量池内的符号引用转换为直接引用的过程。

初始化:初始化阶段是类加载过程的最后一步,主要是根据程序中的赋值语句主动为类变量赋值。

注意:如果有父类并且父类为初始化的时候,先去初始化父类,然后在执行子类初始化语句。

什么时候对类进行初始化?
    1)使用new关键字创建该类实例化对象的时候。
    2)读取或设置类静态字段的时候(在编译时就被放入常量池的静态字段除外)
    3)调用类静态方法的时候
    4)使用反射Class.forName()对类进行反射调用的时候,该类需要初始化。
    5)初始化一个类的时候,有父类,先初始化父类(接口除外,接口只在调用的时候才会被初始化)
    6)被标明为启动类的类,既包含main()方法的类要初始化。
    7)当使用JDK1.7的动态语言支持时,如果一个java.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

二、类加载器:类加载器实现的功能就是加载阶段获取二进制字节流的时候。
    JVM听了三种系统的类加载器。
        1)启动类加载器(Bootstrap ClassLoader):最顶层的类加载器,负责加载JAVA_HOME\bin目录中的,或者-Xbootclasspath参数指定路劲中的,且被虚拟机认可的类。
        2)扩展类加载器(Extension ClassLoader):负责加载JAVA_HOME\lib\ext目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
        3)应用程序类加载器(Application ClassLoader):也叫做系统类加载器,可以通过getSystemClassLoader()获取,负责加载用户路劲(classpath)上的类库。如果没有自定义加载器,一般这个就是默认的类加载器。

类加载器之间的层次关系如下:

类加载器之间的这种层次关系叫做双亲委派模型。
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。
这里的类加载器之间的父子关系一般不是以继承关系实现的。

双亲委派模型的工作过程
    如果一个类接收到类加载请求,他自己不会去加载这个请求,而是将这个类加载请求委派给父类加载器,这样一层一层传送,直到到达启动类加载器。
    只有当父类加载器无法加载这个请求时,子加载器才会尝试自己去加载。

双亲委派模型的代码实现:
    双亲委派模型的代码实现集中在java.lang.ClassLoader的loadClass()方法中。
    1)首先检查类是否被加载,没有则调用父类加载器的loadClass()方法。
    2)若父类加载器为空,则默认使用启动类加载器作为父加载器。
    3)若父类加载失败,抛出ClassNotFoundExcepton异常后,再调用自己的findClass()方法。

代码如下:
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
   //1 首先检查类是否被加载
   Class c = findLoadedClass(name);
   if (c == null) {
       try {
           if (parent != null) {
            //2 没有则调用父类加载器的loadClass()方法;
               c = parent.loadClass(name, false);
          } else {
           //3 若父类加载器为空,则默认使用启动类加载器作为父加载器;
               c = findBootstrapClass0(name);
          }
      } catch (ClassNotFoundException e) {
          //4 若父类加载失败,抛出ClassNotFoundException 异常后
           c = findClass(name);
      }
  }
   if (resolve) {
       //5 再调用自己的findClass() 方法。
       resolveClass©;
  }
   return c;
}
   破坏双亲委派模型:
        双亲委派模型很好的解决了各个类加载器加载基础类的统一性问题。即越基础的类由越上层的加载器          进行加载。 
        若加载的基础类中需要回调用户代码,而这时顶层的类加载器无法识别这些用户代码,怎么办呢?这          时就需要破坏双亲委派模型了。

破坏的过程:
        1、JNDI破坏双亲委派模型
              JNDI是Java标准服务,它的代码由启动类加载器去加载。但是JNDI需要回调独立厂商实现的代                   码,而类加载器无法识别这些回调代码(SPI)。
              为了解决这个问题,引入了一个线程上下文类加载器。 可通过Thread.setContextClassLoader()                设置。
              利用线程上下文类加载器去加载所需要的SPI代码,即父类加载器请求子类加载器去完成类加载的               过程,而破坏了双亲委派模型。

2、Spring破坏双亲委派模型
                Spring要对用户程序进行组织和管理,而用户程序一般放在WEB-INF目录下,由                                         WebAppClassLoader类加载器加载,而Spring由Common类加载器或Shared类加载器加载。
                那么Spring是如何访问WEB-INF下的用户程序呢?
                使用线程上下文类加载器。 Spring加载类所用的classLoader都是通过                                                         Thread.currentThread().getContextClassLoader()获取的。当线程创建时会默认创建一个                           AppClassLoader类加载器(对应Tomcat中的WebAppclassLoader类加载器):                                           setContextClassLoader(AppClassLoader)。
                利用这个来加载用户程序。即任何一个线程都可通过getContextClassLoader()获取到                                   WebAppclassLoader。

class Singleton{
   private static Singleton singleton = new Singleton();
   public static int value1;
   public static int value2 = 0;

   private Singleton(){
       value1++;
       value2++;
  }

   public static Singleton getInstance(){
       return singleton;
  }

}

class Singleton2{
   public static int value1;
   public static int value2 = 0;
   private static Singleton2 singleton2 = new Singleton2();

   private Singleton2(){
       value1++;
       value2++;
  }

   public static Singleton2 getInstance2(){
       return singleton2;
  }

}

public static void main(String[] args) {
       Singleton singleton = Singleton.getInstance();
       System.out.println(“Singleton1 value1:” + singleton.value1);
       System.out.println(“Singleton1 value2:” + singleton.value2);

       Singleton2 singleton2 = Singleton2.getInstance2();
       System.out.println(“Singleton2 value1:” + singleton2.value1);
       System.out.println(“Singleton2 value2:” + singleton2.value2);
  }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值