类加载机制+变量初始化

字节码class文件、可执行代码、机器码的生成与执行:

1)编译器将Java源代码编译成字节码class文件

2)类加载到JVM里面后,执行引擎把字节码转为可执行代码

3)执行的过程,再把可执行代码转为机器码,由底层的操作系统完成执行。

运行Java的几个命令:

1)java用来运行一个.class文件

2)javadoc用来生成api文档

3)jar用来生成jar包

4)javac用来把.java文件编译为.class文件

 

 类加载机制+变量初始化

类从加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括: 加载-验证-准备-解析-初始化-使用-卸载加载-验证-准备自然需要在初始化之前,解析有可能在初始化之后,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定),其他阶段按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。

绑定:把一个方法的调用与方法所在的类(方法主体)关联起来,对java来说,绑定分为静态绑定和动态绑定:

  • 静态绑定:即前期绑定。在程序执行前方法已经被绑定,此时由编译器或其它连接程序实现。可以理解为程序编译期的绑定。只有final,static,private和构造方法是前期绑定的。
  • 动态绑定:即晚期绑定,也叫运行时绑定。在运行时根据具体对象的类型进行绑定,几乎所有的方法都是后期绑定的。
(1) 装载:查找和导入Class文件;

(2) 链接:把类的二进制数据合并到JRE中;

   (a)校验:检查载入Class文件数据的正确性;

   (b)准备:给类的静态变量分配存储空间;

   (c)解析:将符号引用转成直接引用;

(3) 初始化:对类的静态变量,静态代码块执行初始化操作

加载:

将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

类加载机制+变量初始化

类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。

加载.class文件的方式有:

1. 从本地系统中直接加载
2. 通过网络下载.class文件
3. zipjar等归档文件中加载.class文件
4. 从专有数据库中提取.class文件
5. Java源文件动态编译为.class文件

JVM的主要工作:

1)通过一个类的全限定名获取定义此类的二进制字节流。

2)将这个字节流所表示的静态存储结构转化为方法区运行时数据结构。

3)在内存中生成一个代表这个类的 class 对象,作为方法区的各种数据的访问入口

类加载器:

类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类的加载阶段。对于任意一个类,都需要由它的类加载器和这个类本身一同确定其在就Java虚拟机中的唯一性,也就是说,即使两个类来源于同一个Class文件,只要加载它们的类加载器不同,那这两个类就必定不相等

   站在Java虚拟机的角度来讲,只存在两种不同的类加载器:

  • 启动类加载器:它使用C++实现(这里仅限于Hotspot,也就是JDK1.5之后默认的虚拟机,有很多其他的虚拟机是用Java语言实现的),是虚拟机自身的一部分。
  • 所有其他的类加载器:这些类加载器都由Java语言实现,独立于虚拟机之外,并且全部继承自抽象类java.lang.ClassLoader,这些类加载器需要由启动类加载器加载到内存中之后才能去加载其他的类。

    站在Java开发人员的角度来看,类加载器可以大致划分为以下三类:

  • 启动类加载器:Bootstrap ClassLoader,跟上面相同。它负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。
  • 扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。
  • 应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

 应用程序都是由这三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。这几种类加载器的层次关系如下图所示:

类加载机制+变量初始化

 

    这种层次关系称为类加载器的双亲委派模型。我们把每一层上面的类加载器叫做当前层类加载器的父加载器,当然,它们之间的父子关系并不是通过继承关系来实现的,而是使用组合关系来复用父加载器中的代码。

    双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

    使用双亲委派模型来组织类加载器之间的关系,有一个很明显的好处,就是Java类随着它的类加载器(说白了,就是它所在的目录)一起具备了一种带有优先级的层次关系,这对于保证Java程序的稳定运作很重要。例如,类java.lang.Object类存放在JDK\jre\lib下的rt.jar之中,因此无论是哪个类加载器要加载此类,最终都会委派给启动类加载器进行加载,这边保证了Object类在程序中的各种类加载器中都是同一个类。

委派的好处就是避免有些类被重复加载。双亲委派的实现:

 1 protected synchronized Class<?> loadClass(String paramString, boolean paramBoolean)
 2     throws ClassNotFoundException
 3   {
 4        //检查是否被加载过
 5     Class localClass = findLoadedClass(paramString);
 6        //如果没有加载,则调用父类加载器
 7     if (localClass == null) {
 8       try {
 9            //父类加载器不为空
10         if (this.parent != null)
11           localClass = this.parent.loadClass(paramString, false);
12         else {
13              //父类加载器为空,则使用启动类加载器
14           localClass = findBootstrapClass0(paramString);
15         }
16       }
17       catch (ClassNotFoundException localClassNotFoundException)
18       {
19            //如果父类加载失败,则使用自己的findClass方法进行加载
20         localClass = findClass(paramString);
21       }
22     }
23     if (paramBoolean) {
24       resolveClass(localClass);
25     }
26     return localClass;
27   }
View Code

相关文章:

  • 2021-06-27
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-02-14
  • 2021-07-17
猜你喜欢
  • 2021-04-21
  • 2021-06-29
  • 2022-02-05
  • 2022-12-23
  • 2021-10-15
  • 2021-08-22
  • 2021-12-08
相关资源
相似解决方案