JVM虚拟机

  • Java虚拟机有自己完善的硬件架构(处理器、堆栈、寄存器等)和指令系统
  • Java虚拟机是一种能运行Java bytecode的虚拟机
  • JVM并非专属于Java语言,只要生成的编译文件能匹配JVM对载入编译文件格式要求,任何语言都可以交由JVM运行,比如Scala、Groovy、Fantom等,见Java虚拟机维基百科
  • JVM虚拟机除了Sun开发的HotSpot外,还有BEA、IBM、微软、等公司都有开发。见《深入理解Java虚拟机(第二版)》
  • 查看自己用的JVM:cmd->java -version。我的是“Java HotSpot 64-Bit Server VM(build 25.92-b14 mixed mode)”。

JVM和类

  • 当调用java命令运行一个java程序时,就启动了一个Java虚拟机进程,不论该程序多么复杂,占用多大的内存,始终处于该进程中
  • JVM进程合适终止?
    • 程序运行结束
    • 程序执行过程中,遇到未捕获的异常或者错误
    • 程序运行中调用了System.exit()或者Runtime.getRuntime().exit(),退出了虚拟机
    • 程序所在平台强制结束了JVM进程
  • 虚拟机何时加载一个类?
    • 第一次使用该类时
    • 预加载

类的加载、连接、初始化

  • 当系统要使用某个类的时候,会将该类初始化,初始化依次包括加载、连接、初始化三步,一般说类的加载或类的初始化就包含了这三个步骤。
  • 类的加载
    • 概念:类加载器将.class字节码文件读入内存,并创建一个对应的java.lang.Class对象。加载进内存的每个类都有至少一个与之对象的Class对象
    • 类加载器有哪些?
      • Bootstrap ClassLoader:根类加载器,只有这个加载器不是用java语言写的;并且只有这个加载器不是ClassLoader的子类的实例
      • Extension ClassLoader:扩展类加载器
      • System ClassLoader:系统类加载器
      • 自定义类加载器:继承ClassLoader抽象类
    • 可以从哪些地方加载.class字节码文件?
      • 来源于本地文件系统
      • 来自于jar包中
      • 通过网络加载class文件
      • 把一个Java源文件动态编译,并执行加载。这个不懂?
  • 类的连接
    • 概念:负责把二进制数据合并到JRE中
    • 验证阶段:验证被加载的类是否具有正确的内部结构,并和其他类协调一致
    • 准备阶段:为类变量分配内存,并设置默认初始值
    • 解析阶段:将类的二进制数据中的符号引用替换为直接引用
  • 类的初始化
    • 类的初始化,主要是对类变量的初始化
    • 如果这个类还没有被加载和连接,那么先加载并连接。这个主要是针对父类
    • 如果这个类的直接父类还没有初始化,那就先初始化其父类
    • 依次执行类中的初始化语句
    • 因此最先被初始化的类总是java.lang.Object。参见:0023 Java学习笔记-面向对象-初始化代码块
  • JVM在何时初始化一个类
    • 一般说来,在JVM首次使用一个类时,对该类给予初始化,具体包含以下六种情况

      • 创建一个类的实例时:new操作符;反射;反序列化
      • 调用一个类的类方法
      • 访问一个类或接口的类变量
      • 用反射方式来强制创建一个类的Class对象:Class.forName("className");注意ClassLoader的loadClass()方法只会加载而不会初始化该类
      • 某个类的子类被初始化时,该类也会被初始化
      • 直接用java.exe运行某个主类,先初始化该主类。不懂?
    • 不会初始化的情况
      • 宏变量:static final变量,并且能在编译阶段就确定它的值。一个类使用另一个类的宏变量,另一个类不会被初始化。
      • 示例代码01:访问宏变量不会初始化它所在的类

示例代码01:访问宏变量不会初始化它所在的类

package testpack;
public class Test1{
public static void main(String[] args){
System.out.println(A.num); //输出8,没有输出“A类被初始化”,A类没有被初始化
System.out.println(B.num); //输出“B类被初始化”;21;B类被初始化
}
}
class A{
static{
System.out.println("A类被初始化");
}
public static final int num=8; //num的值在编译阶段就能确定下来
}
class B{
static{
System.out.println("B类被初始化");
}
public static final int num=8+Integer.valueOf(13); //num的值不能在编译阶段确定下来
}

类的加载器

  • 如何标识一个被载入JVM的类?

    • 类名+包名+类加载器名
  • 类加载器的层次结构
    • Bootstrap ClassLoader:根类加载器,

      • 没有父加载器;由C++写成,其他加载器都是Java写成;
      • 负责加载Java核心类库;也就是系统属性sun.boot.class.path的值表示的路径下的包;
      • (HotSpot?)可以在java.exe中用-D参数指定系统属性sun.boot.class.path的值,从而加载指定的附加类;
      • 见示例02:获取根类加载器加载的核心类库
    • Extension ClassLoader:扩展类加载器
      • 没有父加载器(实际上就是根类加载器?);由Java写成,是ClassLoader的子类;
      • 负责加载扩展目录JAR包中的类,即系统属性java.ext.dirs或者%JAVA_HOME%/jre/lib/ext
      • 因此可以把自己开发的类,打包成JAR包,放在该目录下
      • 见示例03:扩展类加载器的加载目录,父加载器
    • System ClassLoader:系统类加载器
      • 父加载器是扩展类加载器;由Java写成,是ClassLoader的子类;
      • 也是用户自定义的类加载器的默认父加载器,如果不特别指定的话。
      • 负责加载系统属性java.class.path系统CLASSPATH环境变量指定的目录中的类;
      • Java命令的-classpath参数可以临时指定CLASSPATH的路径
      • 示例04:系统类加载器的加载路径
    • 自定义类加载器:
      • 继承ClassLoader抽象类。
      • 默认的父加载器是系统类加载器;在自定义的时候,可以在一个方法中指定。见java的类加载器ClassLoader
    • 上面说的父加载器,并不是类的继承关系,而是加载器间实例间的关系,就是说在一个加载器中可以定义它的父加载器。

示例代码02:根类加载器加载的核心类库

package testpack;
import java.net.URL;
public class Test1{
public static void main(String[] args)throws ClassNotFoundException{
URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs(); //获取根类加载器加载的全部URL数组
for (int i=0;i<urls.length;i++){
System.out.println(urls[i].toExternalForm());
}
System.out.println("-------------下面是系统属性(sun.boot.class.path)的值---------------");
System.out.println(System.getProperty("sun.boot.class.path"));
}
}

输出:

file:/C:/Java/jdk1.8.0_92/jre/lib/resources.jar

file:/C:/Java/jdk1.8.0_92/jre/lib/rt.jar //核心类库java.lang.*位于该jar包中

file:/C:/Java/jdk1.8.0_92/jre/lib/sunrsasign.jar

file:/C:/Java/jdk1.8.0_92/jre/lib/jsse.jar

file:/C:/Java/jdk1.8.0_92/jre/lib/jce.jar

file:/C:/Java/jdk1.8.0_92/jre/lib/charsets.jar

file:/C:/Java/jdk1.8.0_92/jre/lib/jfr.jar

file:/C:/Java/jdk1.8.0_92/jre/classes

-------------下面是系统属性(sun.boot.class.path)的值---------------

file:/C:/Java/jdk1.8.0_92/jre/lib/resources.jar

file:/C:/Java/jdk1.8.0_92/jre/lib/rt.jar

file:/C:/Java/jdk1.8.0_92/jre/lib/sunrsasign.jar

file:/C:/Java/jdk1.8.0_92/jre/lib/jsse.jar

file:/C:/Java/jdk1.8.0_92/jre/lib/jce.jar

file:/C:/Java/jdk1.8.0_92/jre/lib/charsets.jar

file:/C:/Java/jdk1.8.0_92/jre/lib/jfr.jar

file:/C:/Java/jdk1.8.0_92/jre/classes

示例03:扩展类加载器的加载目录,父加载器

package testpack;
public class Test1 {
public static void main(String[] args){
ClassLoader systemLoader=ClassLoader.getSystemClassLoader(); //获取系统类加载器
System.out.println("这是系统类加载器: "+systemLoader); //输出系统类加载器
ClassLoader extensionLoader=systemLoader.getParent(); //获取扩展类加载器
System.out.println("这是扩展类加载器: "+extensionLoader); //输出扩展类加载器
System.out.println("扩展类的父加载器: "+extensionLoader.getParent()); //获取扩展类加载器的父加载器null
System.out.println("扩展类的加载路径: "+System.getProperty("java.ext.dirs")); //获取系统属性java.ext.dirs的值
}
}

输出:

这是系统类加载器: sun.misc.Launcher$AppClassLoader@73d16e93 //说明系统类加载器是AppClassLoader的实例

这是扩展类加载器: sun.misc.Launcher$ExtClassLoader@15db9742 //说明扩展类加载器是ExtClassLoader的实例

扩展类的父加载器: null //以上二者都是URLClassLoader的实例

扩展类的加载路径: C:\Java\jdk1.8.0_92\jre\lib\ext;C:\windows\Sun\Java\lib\ext

示例04:系统类加载器的加载路径

package testpack;

import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.lang.ClassLoader; public class Test1 {
public static void main(String[] args)throws IOException{
ClassLoader systemLoader=ClassLoader.getSystemClassLoader(); //获取系统类加载器
System.out.println("系统类加载器: "+systemLoader); //输出系统类加载器
Enumeration<URL> eml=systemLoader.getResources(""); //遍历其加载路径
while(eml.hasMoreElements()){
System.out.println(eml.nextElement());
}
System.out.println("系统属性java.class.path的值: "+System.getProperty("java.class.path"));
}
}

输出:

系统类加载器: sun.misc.Launcher$AppClassLoader@73d16e93

file:/D:/JavaWorkspace/Test/bin/

系统属性java.class.path的值: D:\JavaWorkspace\Test\bin

类的加载机制

  • 全盘负责制:

    • 当一个类加载器加载一个类时,该类依赖和引用的其他类,也有这个类加载器负责
  • 父类委托:
    • 先让父加载器加载这个类,如果加载不了,再从自己的类路径中加载。不懂?,一般说来,不同的加载器的加载路径都不同吗?
  • 缓存机制:
    • 要使用一个类时,先从缓存中查找,如果已经有了,就不另行加载,直接返回;如果没有,再加载

类加载器的8个步骤

  • 要加载的类是否已被加载?

      • 如果父类加载器不存在,那么请求使用根类加载器加载

        • 成功:返回Class对象
        • 失败:抛出ClassNotFoundException
      • 如果父类加载器存在,请求使用父加载器加载
        • 成功:返回Class对象
        • 失败:当前类加载器载入类
          • 当前类寻找加载

            • 成功:返回Class对象
            • 失败:抛出ClassNotFoundException
    • 是:返回其Class对象
  • 总的来说,先在缓存中找;再让父加载器加载,一直到没有父加载器,就用根加载器加载,如果父加载器失败,那么自己加载,还失败,那就抛出异常:ClassNotFoundException

自定义类加载器

  • 继承结构
  • java.lang.Object
    • java.lang.ClassLoader

      • java.security.SecureClassLoader

        • java.net.URLClassLoader

          • sun.misc.Launcher$ExtClassLoader //扩展类加载器就是这个类的实例
          • sun.misc.Launcher$AppClassLoader //系统类加载器就是这个类的实例
  • 所有的类加载器中,除了根加载器外,都是ClassLoader子类的实例。
  • ClassLoader类包含了一些protected和static方法
  • ClassLoader的主要方法:
    • protected Class<?> loadClass(String name,boolean resolve);

      • 根据指定名称加载类,返回指定类的Class对象
      • 自定义类加载器时,最好不要重写该方法
      • 执行步骤:
        • 用findLoadedClass(String)检查是否已加载
        • 在父加载器上调用loadClass()方法,若父加载器为null,则用根加载器加载
        • 用findClass(String)查找类
    • protected Class<?> findClass(String name);
      • 根据名称查找类
      • 自定义类加载器时,一般只重写这个方法
    • protected final Class<?> defineClass(String name, byte[] b, int off, int len)
      • 将指定的字节码文件读入byte数组,转换为Class对象
      • final,不可重写
    • protected final Class<?> findSystemClass(String name)
      • 从本地文件系统装入字节码文件
    • static ClassLoader getSystemClassLoader()
      • 返回系统类加载器
    • final ClassLoader getParent()
      • 返回该加载器的父加载器
      • final,不可重写
    • protected final void resolveClass(Class<?> c)
      • 链接指定的类c
      • 不懂?
    • protected final Class<?> findLoadedClass(String name)
      • 如果已经加载了名为name的类,则返回其Class实例

其他

  • 关于类加载器实例、类加载器实例的类、Class对象、普通类、普通类的对象

    • 简单的说,加载器是实例,他们的类有AppClassLoader和ExtClassLoader,都是URLClassLoader的子类
    • ExtClassLoader类的实例就是扩展类加载器,ExtClassLoader自身在加载的时候也有Class对象
    • AppClassLoader类的实例就是系统类加载器,AppClassLoader自身在加载的时候也有Class对象
    • ExtClassLoader和AppClassLoader二者没有继承关系,只是前者的实例是后者的实例的父加载器
    • 系统类加载器,加载一个普通类,创建对应的Class对象,连接,初始该普通类,再创建实例
    • 看下面的示例代码
  • 什么情况下需要自定义类加载器?见JVM——自定义类加载器 CSDN
  • 参考:深入探讨Java类加载器 IBM 成富
package testpack;
public class Test1 {
public static void main(String[] args){
A a=new A(); //创建普通类A的实例a
a.show(); //调用a的show()方法
ClassLoader cl=a.getClass().getClassLoader(); //通过实例a得到A类的Class对象,再获得该Class对象的加载器
System.out.println("实例a的类A的Class对象的加载器是: "+cl); //输出加载器,是AppClassLoader类的实例
Class appClazz=cl.getClass(); //通过系统类加载器实例获得它的类的Class对象
while(appClazz!=null){
System.out.println(appClazz); //输出该Class对象
appClazz=appClazz.getSuperclass(); //获得这个Class对象的父类的Class对象,一直到Object没有父类
}
}
}
class A{
public void show(){
System.out.println("这是A类的实例");
}
}

参考资料:

0032 Java学习笔记-类加载机制-初步的更多相关文章

  1. JAVA 学习笔记 - 反射机制

    1.   JAVA反射机制的概念 2. 怎样实例化一个 Class对象 Class.forName(包名.类名); 对象.getClass(); 类.class; ================== ...

  2. Java学习笔记-反射机制

    Java反射机制实在运行状态时,对于任意一个类,都能够知道这个类的属性和方法,对于任意一个对象,都能够调用他的任意一个属性和方法 获取Class对象的三种方式 Object类中的getClass()方 ...

  3. Java学习笔记--异常机制

    简介 在实际的程序运行过程中,用户并不一定完全按照程序员的所写的逻辑去执行程序,例如写的某个模块,要求输入数字,而用户却在键盘上输入字符串:要求打开某个文件,但是文件不存在或者格式不对:或者程序运行时 ...

  4. java学习笔记09--反射机制

    java学习笔记09--反射机制 什么是反射: 反射是java语言的一个特性,它允许程序在运行时来进行自我检查并且对内部的成员进行操作.例如它允许一个java的类获取他所有的成员变量和方法并且显示出来 ...

  5. java学习笔记13--反射机制与动态代理

    本文地址:http://www.cnblogs.com/archimedes/p/java-study-note13.html,转载请注明源地址. Java的反射机制 在Java运行时环境中,对于任意 ...

  6. 《Java学习笔记(第8版)》学习指导

    <Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...

  7. Java学习笔记4

    Java学习笔记4 1. JDK.JRE和JVM分别是什么,区别是什么? 答: ①.JDK 是整个Java的核心,包括了Java运行环境.Java工具和Java基础类库. ②.JRE(Java Run ...

  8. 20145230《java学习笔记》第九周学习总结

    20145230 <Java程序设计>第9周学习总结 教材学习内容 JDBC JDBC简介 JDBC是用于执行SQL的解决方案,开发人员使用JDBC的标准接口,数据库厂商则对接口进行操作, ...

  9. java学习笔记之基础篇

    java选择语句之switch   //switch可以用于等值判断 switch (e) //int ,或则可以自动转化成int 的类型,(byte char short)枚举jdk 7中可以防止字 ...

随机推荐

  1. 1Z0-053 争议题目解析470

    1Z0-053 争议题目解析470 考试科目:1Z0-053 题库版本:V13.02 题库中原题为: 470.Which NLS parameter can be used to change the ...

  2. 前端开发编辑器(notepad++、sublime text)

    1.Notepad++ 正则替换: 如<td>第三节</td> 替换成<td><input type="text" value=" ...

  3. CLR中的程序集加载

    CLR中的程序集加载 本次来讨论一下基于.net平台的CLR中的程序集加载的机制: [注:由于.net已经开源,可利用vs2015查看c#源码的具体实现] 在运行时,JIT编译器利用程序集的TypeR ...

  4. KVM的前世今生

    1.虚拟化技术的演变过程:软件模拟.虚拟化层翻译.容器虚拟化三个阶段 (1)软件模拟的技术方式 软件模拟是通过软件完全模拟CPU.网卡.芯片组.磁盘等计算机硬件,因为是软件模拟,所以理论上可以模拟任何 ...

  5. Azure Backup (2) Azure备份服务

    <Windows Azure Platform 系列文章目录> 本文介绍的是国内由世纪互联运维的Azure China. 本文介绍的Azure管理界面是Classic Model,网址:h ...

  6. .NET 对象生命周期

    GC 垃圾回收      .NET Framework 的垃圾回收器管理应用程序的内存分配和释放.每次您使用 new 运算符创建对象时,运行库都从托管堆为该对象分配内存.只要托管堆中有地址空间可用,运 ...

  7. Myeclipse启动报错: Invalid 'log4jConfigLocation' parameter

    java.lang.IllegalArgumentException: Invalid 'log4jConfigLocation' parameter: class path resource [lo ...

  8. eclipse 突然 一直在loading descriptor for XXX (XXX为工程名)

    问题: eclipse 启动后,啥也不干,就一直在loading descriptor for XXX (XXX为工程名),,其他什么操作都不能操作. 如下图所示,保存文件也无法保存.  这个怎么办? ...

  9. 转:Java Web应用中调优线程池的重要性

    不论你是否关注,Java Web应用都或多或少的使用了线程池来处理请求.线程池的实现细节可能会被忽视,但是有关于线程池的使用和调优迟早是需要了解的.本文主要介绍Java线程池的使用和如何正确的配置线程 ...

  10. mariadb 10.2.3支持oracle execute immediate语法

    在之前的版本包括oracle mysql/percona server版本中,所有的动态SQL都需要通过prepare执行,如下: "; execute stmt; deallocate p ...