`
han1202009
  • 浏览: 9558 次
社区版块
存档分类
最新评论

类加载机制ClassLoader

    博客分类:
  • JVM
阅读更多

可视化编辑器做的太垃圾了 , 把word笔记放在了附件里面

 

ClassLoader主要作用 : 将.class后缀的字节码文件从硬盘上装载到内存中 ;

1.类加载器深入剖析

Java虚拟机与程序的生命周期 :
      当我们执行一个java程序的时候 , 会启动一个JVM进程 , 当程序执行完之后 , JVM进程就消亡了 ;
在如下情况下JVM将结束声明周期 :

  • System.exit(int)方法 , 当执行这个方法的时候 , 虚拟机会退出 ; 这个方法传入一个整形参数 , 这个参数是状态吗 :  如果这个整形是 0 的话 , 就是正常退出 , 如果不是0的话 , 就是异常退出 ;
  • 程序正常结束 ;
  • 程序执行过程中 , 遇到了异常或错误 , 而异常终止 : 如果我们在程序中出现了异常 , 而不去处理 , 会将异常一直抛给main函数 , main函数会将异常抛给JVM , JVM如果处理不了异常 , JVM就会异常退出 ;
  • 由于操作系统出现错误导致JVM进程终止 : JVM所依赖的平台出现错误 , 导致JVM终止 ;

 

2.类的加载,连接和初始化

  • 加载 : 查找并加载类的二进制数据 , 将class字节码文件加载到内存中 ;
  • 连接  :

  •             -验证 : 确保被加载的类的正确性 , 使用javac 编译工具生成的字节码文件能通过验证 , 如果不是由javac编译生成的字节码文件 , 如果自己生成的字节码文件不符合JVM虚拟机对字节码文件的要求的话 , 可能会出现验证通不过的情况  ; 比如说随便拿一个文件 , 将后缀名直接修改为.class , 这样的字节码文件肯定不合法 ;
                -准备 : 为类的静态变量分配内存 , 并将其初始化为默认值 ;
                -解析 : 把类中的符号引用转为直接引用 ;
  • 初始化  : 为类的静态变量赋予正确的初始值(正确的值指的是用户赋的值) ;
  •             -好像这个与连接阶段的准备有些重复 , 在连接的准备阶段只是赋予初始变量 , 如果用户给这个变量赋了初始值 , 那么这个变量在连接的准备阶段仍然会赋予初始值 ;
                -在这个阶段 , 才会真正的将初始值赋给静态变量 ;




      Java程序对类的使用方式有 主动使用 和 被动使用 ;
      所有的JVM实现 , 必须在每个类或者接口 , 被java程序 “首次主动使用” 时才初始化他们 ;
主动使用 :

  •  创建类的实例 ;
  •  访问某个类或接口的静态变量 , 或者对该静态变量赋值 ;
  •  调用类的静态方法 ;
  •  反射 : Class.forName(“类名”) ;
  •  初始化一个类的子类 , 看做对父类的主动使用 ; 
  •  java虚拟机启动的时候 , 被标明启动类的类 , 即包含main方法的类 , 程序的入口 ;



      除了上面6种主动使用之外 , 其它的情况均为被动使用 , 其它情况都不会执行第三步初始化 ;

 

 

 

3.类的加载

(1)概念

 

  • 类的加载 : 指的是将类的.class文件中的二进制数据读入到内存 , 将其放在运行时数据区的方法区 , 然后再堆区创建一个java.lang.Class对象 , 用来封装类在方法区内的数据结构 ;
  • 反射 : 反射就是跟句堆区的字节码文件 , 获取方法去的数据结构 ;
  • 解析 : Class对象是由JVM自己创建的 , 所有的对象都是经过Class对象创建 , 这个Class对象是反射的入口 , 通过Class对象 , 可以关联到目标class字节码文件的内部结构 ;

 

    所有的类对应的Class对象都是唯一的一个 , 这个类是由JVM进行创建的 , 并且只有JVM才会创建Class对象

 


类加载的最终产品是位于堆区中的Class对象 , Class对象封装了类在方法区内的数据结构 , 并且向Java程序员提供了访问方法区内的数据结构的接口(反射用的接口) ;

 

 

(2)加载.class文件的方式

从本地系统中直接加载 : 编译好的.class字节码文件直接从硬盘中加载 ;

通过网络下载.class文件 : class字节码文件放在网络空间中 , 使用URLClassLoader来加载在网络上的.class字节码文件 , 使用默认的父亲委托机制加载字节码文件 ;

zip , jar 等压缩文件中加载字节码文件 : 在开发的时候 , 导入jar , 就是这种方式 ;

从专有的数据库中提取字节码文件 ;

java源文件动态编译为字节码文件 ;

 

(3)类加载器

  l  Java虚拟机自带的类加载器 :

     -根类加载器 ( Bootstrap ) : C++写的 , 程序员无法再java代码中获取这个类 , 如果使用getClassLoader()方法获取到的是一个null ;

     package jvm;

public class ClassLoaderTest {
	public static void main(String[] args) throws Exception {
		//java.lang包下的类使用的是跟类加载器进行加载的
		Class clazz = Class.forName("java.lang.String");
		System.out.println(clazz.getClassLoader());
		//自定义的类使用的是应用类加载器(系统加载器)
		Class clazz2 = Class.forName("jvm.C");
		System.out.println(clazz2.getClassLoader());
	}
}
class C{} 

 执行结果 : 

   null

sun.misc.Launcher$AppClassLoader@1372a1a

     -扩展类加载器 ( Extension ) : Java编写 ;

  -系统类加载器(应用加载器) ( System )  : Java编写

 

 

用户自定义的类加载器 :

-自定义的类加载器都是java.lang.ClassLoader子类 ;

-用户可以定制类的加载方式

 

      String类是由根类加载器进行加载的 , 我们可以调用Class对象的

 

关于代理中创建对象的类加载器 : 创建代理对象的时候 , 动态创建一个类 , 然后使用指定的类加载器将这个类加载到内存中 , 然后用加载到内存中的类生成代理对象 ;

创建代理对象的方法 : newProxyInstance(ClassLoader loader , Class [] Interfaces , InvocationHandler h )

      loader 是定义的代理类的类加载器 , 中间的接口数组是代理类的要实现的接口列表 , h 是指派方法调用的调用处理程序 ;

 

类加载器并不需要在某个类被首次主动使用时再加载它 :

-预加载机制 : JVM规范允许类加载器在预料某个类将要被使用的时就预先加载它 ;

-报错时机 : 如果在预加载的过程中遇到了字节码文件缺失或者存在错误的情况 , 类加载器会在程序首次主动使用(上面提到的六种情况)类的时候报错(LinkageError错误) ;

-不报错时机 : 如果这个错误的字节码文件所对应的类一直没有被使用 , 那么类加载器就不会报告错误 , 即便有错误也不会报错 ;

 

LinkageError : 这个错误是Error的子类 , 程序不能处理这些错误 , 这些错误都是由虚拟机来处理 , 这个错误表示出错的是子类 , 在一定程序上依赖于另一个类 , 在编译了前面一个类的时候 , 与后面所依赖的类出现了不兼容的情况 ;

例如 : 我们使用了jdk 1.6 在编译一个程序 , 但是运行环境是jre1.5 , 就会出现LinkageError错误 ;

4.类的连接

(1)定义

类被加载之后 , 就进入链接阶段 ; 链接 : 将已读入内存的二进制数据合并到虚拟机的运行时环境中去 ;

   链接顾名思义就是讲类与类之间进行关联 , 例如我们在类A中调用了类B , 在链接过程中 , 就将AB进行链接 , 将面向对象语言转化为面向过程语言 ;

  

(2)类的验证

类文件的结构检查 : 确保类文件遵从java类文件的固定格式 , 开始类的描述 , 声明 , 方法调用格式等 ;

语义检查 : 确保类本身符合java语言的语法规定 , 比如final类型的类没有子类 , final类型的方法没有被覆盖 , eclipse中这种错误编译的时候不能通过 , 但是通过其他的方法可以生成错误的字节码文件 , 这里就是检测恶意生成的字节码文件 ;

字节码验证 : 确保字节码流可以被JVM安全的执行 , 字节码流代表java方法(包括静态方法和实例方法) , 它是由被称作操作码的单字节指令组成的序列 , 每一个操作码后面跟着一个或多个操作数 , 字节码验证步骤会检查每个操作码是否合法 , 即是否有着合法的操作数 ;

下面是指令码组成的序列 , 类似于微指令 : 

 

// Compiled from ByteToCharCp1122.java (version 1.5 : 49.0, super bit)
public class sun.io.ByteToCharCp1122 extends sun.io.ByteToCharSingleByte {
  
  // Field descriptor #17 Lsun/nio/cs/ext/IBM1122;
  private static final sun.nio.cs.ext.IBM1122 nioCoder;
  
  // Method descriptor #18 ()Ljava/lang/String;
  // Stack: 1, Locals: 1
  public java.lang.String getCharacterEncoding();
    0  ldc <String "Cp1122"> [1]
    2  areturn
      Line numbers:
        [pc: 0, line: 25]
  
  // Method descriptor #2 ()V
  // Stack: 2, Locals: 1
  public ByteToCharCp1122();
     0  aload_0 [this]
     1  invokespecial sun.io.ByteToCharSingleByte() [25]
     4  aload_0 [this]
     5  getstatic sun.io.ByteToCharCp1122.nioCoder : sun.nio.cs.ext.IBM1122 [23]
     8  invokevirtual sun.nio.cs.ext.IBM1122.getDecoderSingleByteMappings() : java.lang.String [27]
    11  putfield sun.io.ByteToCharSingleByte.byteToCharTable : java.lang.String [24]
    14  return
      Line numbers:
        [pc: 0, line: 28]
        [pc: 4, line: 29]
        [pc: 14, line: 30]
  
  // Method descriptor #2 ()V
  // Stack: 2, Locals: 0
  static {};
     0  new sun.nio.cs.ext.IBM1122 [15]
     3  dup
     4  invokespecial sun.nio.cs.ext.IBM1122() [26]
     7  putstatic sun.io.ByteToCharCp1122.nioCoder : sun.nio.cs.ext.IBM1122 [23]
    10  return
      Line numbers:
        [pc: 0, line: 22]
}

 l  二进制兼容性的验证 : 确保相互引用的类之间协调一致的 ; 例如在A类的a()方法中调用B类的b()方法 , JVM在验证A类的时候 , 会验证B类的b()方法 , 加入b()方法不存在 , 或者版本不兼容(A,B两类使用不同的JDK版本编译) , 会抛出NoSuchMethodError错误 ;

 

 

(3)准备阶段

   在准备阶段 , JVM为类的静态变量分配内存空间 , 并设置默认的初始值 . 例如下面的Sample , 在准备阶段 , int类型的静态变量分配4个字节 , 并赋予初始值 0  ; long 类型的静态变量 b , 分配8个字节 , 并赋予初始值 0 ;

   PS : java中基本类型变量占用的空间是一定的 , java运行在JVM上的 , C , 就要根据平台变化而变化了 ;

 

    public class Sample {

	private static 	int a = 1 ;
	private static long b ;
	static {
		b = 2 ;
	}
}

 (4)类的解析

   在解析阶段 , JVM 会把类的二进制数据中的符号引用替换为直接引用 , 例如在A类中的a()方法引用B类中的b()方法 ;

   A类的二进制数据中包含了一个对B类的b()方法的符号引用 , 这个符号引用由b()方法的全名和相关的描述符组成 , Java解析阶段 , 就会把这个符号引用替换为指针 , 这个指针就是C语言中的指针了 , 该指针指向B类的b()方法在方法区中的内存位置 , 这个指针就是直接引用 ;

 

5.类的初始化

   在初始化阶段 , Java虚拟机执行类的初始化操作 , 为类的静态变量赋予初始值 , 在程序中 , 静态变量初始化有两种途径 :

直接在声明处进行初始化 , 例如下面的Sample中的 变量a ;

在静态代码块中进行初始化 , 例如下面的Sample中的变量b ; 

 

public class Sample {
	private static 	int a = 1 ;
	private static long b ;
	static {
		b = 2 ;
	}
}
 

 

6.面试题介绍

 

public class PrepareOrInit {
	public static void main(String[] args) {
		Singleton singleton = Singleton.getInstance();
		System.out.println(singleton.count1);
		System.out.println(singleton.count2);
	}
}
class Singleton{
	private static Singleton singleton = new Singleton() ;
	public static int count1 ;
	public static int count2 = 0 ;
	private Singleton(){
		count1 ++ ;
		count2 ++ ;
	}
	public static Singleton getInstance(){
		return singleton ;
	}
}

 执行结果 : 1    0 

 

分析 : 这段代码与类的链接中的准备阶段 初始化阶段 有关系 , 准备阶段是给静态的字段赋予默认值 , 初始化阶段给静态变量赋予正确的值 , 即用户的值 ;

在主函数中 , 调用了类的静态方法 , 相当于主动使用 , 这里调用了类的静态方法 ;

之后进行连接的准备操作 , 给类中的静态变量赋予初值 , singleton值为null , count1 count2 值为0 ;

执行初始化操作 , 给类中的静态变量赋予正确的值 , singleton变量赋予正确的值 , 调用构造方法 , 此时count1 count2执行自增操作 , 两者都变成1 , 然后执行count1的赋予正确值操作 , 这里用户没有赋值操作 , count2 用户进行了 赋值为0的操作 , 0将原来的1覆盖掉了 , 因此结果为 1 , 0 ;

 

 

 

public class PrepareOrInit {
	public static void main(String[] args) {
		Singleton singleton = Singleton.getInstance();
		System.out.println(singleton.count1);
		System.out.println(singleton.count2);
	}
}
class Singleton{
	public static int count1 ;
	public static int count2 = 0 ;
	private static Singleton singleton = new Singleton() ;
	private Singleton(){
		count1 ++ ;
		count2 ++ ;
	}
	public static Singleton getInstance(){
		return singleton ;
	}
}

 执行结果 : 1   1 

 

 

在准备阶段count1 count2 都赋值为0 , 然后在初始化阶段 , 全部赋值为1 ;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 大小: 13 KB
  • 大小: 36.2 KB
分享到:
评论
1 楼 fly_宇光十色 2015-11-10  
  这么说变量的声明顺序会影响到最终结果!!

相关推荐

    Java 类加载机制 ClassLoader Class.forName.pdf

    ### Java 类加载机制详解 #### 一、引言 Java 类加载机制是Java虚拟机(JVM)中的一个重要组成部分,它负责将编译后的字节码文件(.class)加载到内存中,形成Class对象,以便于Java程序能够识别并使用这些类。深入...

    tomcat 类加载机制 —— ClassLoader

    《Tomcat类加载机制——ClassLoader详解》 在Java Web开发中,Tomcat作为最常用的Servlet容器,其类加载机制对于理解和优化应用性能至关重要。本文将深入探讨Tomcat的ClassLoader是如何工作的,以及它如何影响到...

    ClassLoader类加载机制和原理详解

    开发者可以通过继承java.lang.ClassLoader类,创建自己的类加载器。这在处理动态加载、插件系统、隔离应用域等场景时非常有用。自定义类加载器需要覆盖`findClass()`方法,实现类的查找和加载逻辑。 4. 双亲委派...

    深入研究Java类加载机制 深入研究Java类加载机制

    ### 深入研究Java类加载机制 #### 一、Java类加载机制概述 Java类加载机制是Java程序运行的第一步,它对于理解Java虚拟机(JVM)的行为至关重要。类加载过程涉及到类的加载、链接(验证、准备、解析)、初始化等...

    ClassLoader类加载机制

    类加载器是 Java 语言的一个创新,也是 ...不过如果遇到了需要与类加载器进行交互的情况,而对类加载器的机制又不是很了解的话,就很容易花大量的时间去调试 ClassNotFoundException和 NoClassDefFoundError等异常。

    ClassLoader(类加载机制)1

    1. **ClassLoader(类加载机制)** Java类加载过程分为三个阶段:加载、链接和初始化。`ClassLoader`在加载阶段发挥作用,它负责查找并加载类的字节码。在Java中,类加载器主要有三层:Bootstrap ClassLoader(引导...

    Java类加载机制与反射-PPT

    Java的类加载机制:加载,连接,初始化。JAVA类加载器: Bootstrap ClassLoader : 根类加载器, Extension ClassLoader: 扩展类加载器, System ClassLoader : 系统类加载器, Java反射

    java类加载机制

    ### Java 类加载机制详解 Java 类加载机制是Java运行时环境的一个核心组成部分,它负责将编译后的Java字节码加载到JVM中,并确保Java应用程序能够正确地运行。类加载机制不仅涉及到类的加载、验证、准备、解析和...

    深入java虚拟机(七)深入源码看java类加载器ClassLoader 1

    源码中,ClassLoader类维护了一个`parent`属性,用于指向其父类加载器。在创建ClassLoader实例时,如果没有指定父加载器,那么`parent`会被设置为系统类加载器。这是通过调用`getSystemClassLoader()`方法实现的,该...

    java的ClassLoader类加载器机制

    Java的ClassLoader类加载器机制 在 Java 虚拟机(JVM)中,类加载器(ClassLoader)扮演着非常重要的角色。类加载器负责加载 Java 类,包括核心类和用户自定义类。在 JVM 运行过程中,类加载器会形成一个层次结构,...

    java类加载机制原理与实现

    Java 类加载机制原理与实现 Java 类加载机制是 Java 虚拟机(JVM)的一部分,负责将编译后的 Java 字节码文件加载到 JVM 中,以便执行 Java 程序。类加载机制是 JVM 的核心组件之一,对 Java 程序的执行和安全性起...

    类加载机制及反射

    Java语言的类加载机制是其运行时环境的重要组成部分,它负责将类的字节码文件从磁盘、网络或其他数据源加载到JVM(Java虚拟机)中,并转化为可以执行的Java对象。类加载机制包括加载、验证、准备、解析和初始化五个...

    Android-Android热修复框架基于类加载机制的代码修复

    本篇文章将深入探讨Android热修复框架的工作原理,特别是基于类加载机制的代码修复。 Android热修复的核心在于替换或修补运行时应用中的问题代码,而实现这一目标的关键是类加载器(ClassLoader)。在Java和Android...

    【图解版】深入分析ClassLoader类加载工作机制

    【图解版】深入分析ClassLoader类加载工作机制,从原理到JVM的装载过程,详情分析了ClassLoader加载类以及自定义类加载器的过程,不可用于商业用途,如有版权问题,请联系删除!

    JVM类加载机制详细讲解

    总结,JVM 类加载机制是Java平台的核心特性之一,它确保了程序的稳定运行和动态扩展能力。理解类加载器的工作原理和双亲委派模型对于优化程序性能、解决类冲突以及构建复杂的模块化系统至关重要。在实际开发中,掌握...

    java应用程序类加载器,ClassLoader for java Application

    例如,可以创建自定义类加载器来实现按需加载、隔离不同版本的库,或者实现动态加载插件机制。通过重写`loadClass()`方法,开发者可以控制类的加载过程,实现特定的加载策略。 **多平台选择性配置**: Java的一个...

    ClassLoader类加载器

    在Java编程语言中,ClassLoader是一个至关重要的组成部分,它负责加载类到JVM(Java虚拟机)中。了解和掌握ClassLoader的工作原理以及如何自定义ClassLoader对于深入理解Java应用程序的运行机制非常有帮助。以下是对...

    java自定义类加载classloader文档,包括代码

    本文将深入探讨Java中的类加载机制,并通过一个具体的自定义类加载器的例子来帮助理解其工作原理。 #### 二、Java类加载器的基本概念 Java中的类加载器主要负责完成以下三个基本任务: 1. **加载(Loading)**:...

    java注解_反射_字节码_类加载机制.zip

    Java注解、反射、字节码和类加载机制是Java编程中的核心概念,它们在实际开发中扮演着重要角色。让我们深入探讨这些知识点。 **Java注解(Annotation)**: Java注解是一种元数据,它提供了在编译时或运行时处理代码的...

Global site tag (gtag.js) - Google Analytics