JVM 学习 第一天

现在的Java因为过于封装,导致当下大多数程序员一直处在上层,一直未研究底层核心。

  • 作用:

    • Java虚拟机是用来执行字节码文件的,用来解析字节码进行运行。
  • 特点:

    • 一次编译,到处运行
    • 自动内存管理
    • 自动垃圾回收功能

优点: 相比C而言,不需要关注内存回收,只需要关注业务逻辑。

缺点: 弱化了程序员的个人能力,降低程序员的门槛,遇到灾难性问题不知原因。

image-20201206145812635

JVM整体结构:

  • HotSpot VM 是目前市面上高性能虚拟机代表作之一。
  • 它采用解释器与即时编译器并存的架构。
  • 在今天,Java程序的运行性能早已脱胎换骨,已经达到可以和C/C++程序一教高下的地步。

Java 代码执行流程

image-20201206150606106

计算机只能识别机器指令,而Java属于高级语言,高级语言需要向下转,只要最终转为机器指令就可以正常运行。

类加载子系统

image-20201206152059635

类加载器和加载过程

分为三个阶段,加载阶段-》链接阶段-》初始化阶段

  • 类加载器子系统主要负责从文件系统或者网络中加载Class文件,class文件在文件中开头有特定的文件标识:咖啡宝贝。
  • ClassLoader只负责class文件加载,至于它是否可以运行,则由ExecutionEngine决定。
    加载的类信息存放于一块称为方法去的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)

image-20201206154012379

  • class file 经过编译之后存在硬盘中,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候需要加载到JVM当中来,根据这个文件实例化出N个一模一样的实例。
  • class file 加载到JVM中,被成为DNA元数据模板,放在方法区。
  • 在.class 文件->JVM-> 最终成为元数据模板,此过程就要一个运行工具(类装载器 Class Loader),扮演一个快递员的角色。

image-20201206154538141

  • 加载:

    • 通过一个类的权限定名获取定义此类的二进制字节流
    • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
    • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
  • 链接:

    • 验证:

      • 目的在于确保class文件的字节码中包含信息符合当前虚拟机要求保证被加载类的正确性,不会危害虚拟机自身安全。
      • 主要包括四种验证,文件格式验证、元数据验证、字节码验证、符号引用验证。
    • 准备(prepare):

      • 为类变量分配内存并且设置该变量的默认初始值,即零值,如果为引用对象,那么为null。
      • 这里不包含用finnal修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化。
      • 这里不会为实例变量分配初始化,类变量会分配到方法区中,而实例变量是会随着对象一起分配到java堆中。
    • 解析:

      • 将常量池中的符号引用转为直接引用的过程。
      • 事实上,解析操作往往会伴随着JVM在执行完出hi话之后再执行。
      • 符号引用就是一组符号来描述所引用的目标,符号引用的字面量形式明确定义在《Java虚拟机规范》的Class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
      • 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量中的CONSTANT_Class_info、CONSTANT_Fieldref_info,CONSTANT_Methodref_info等。
  • 初始化:

    • 初始化阶段就是执行类构造器方法<clinit>()过程。
    • 此方法不需定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。
    • 构造器方法中指令按语句在源文件中出现的顺序执行。
    • <clinit>()不同于类的构造器。(关联:构造器是虚拟机视角下的<init>())
    • 若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕。
    • 虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁。
      *:如果在类中没有类变量或者类代码块,javac则不会生成<clinit>()方法。任何类无论代码中是否显示的代码构造器,都会自动生成一个构造器。

需要注意的是:一个类只会加载一次。如果在多线程的情况下比如 实例化一个对象,当类首次访问时会对这个类进行加载到方法区中,只有一个线程会进行加载,如果持续加载或者加载时间过长,那么后面的线程都会进入阻塞阶段,等待类加载完毕才会继续执行。

类与类加载器

每一个类都拥有一个独立的类名称空间,如果多个加载器加载同一个类,那么这些类之间,都是不等的。包括equals()、isAssignableFrom()方法

public class Test {
    public static void main(String[] args) throws Exception{
        ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = getClass().getResourceAsStream(fileName);
                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };
        Object obj = myLoader.loadClass("deepclone.Test").newInstance();
        System.out.println(obj.getClass());
        System.out.println(obj instanceof deepclone.Test);
    }
}

class deepclone.Test
false

这是因为Java虚拟机中同时存在了两个Test类,一个是由虚拟 机的应用程序类加载器所加载的,另外一个是由我们自定义的类加载器加载的,虽然它们都来自同一 个Class文件,但在Java虚拟机中仍然是两个互相独立的类,做对象所属类型检查时的结果自然为 false。

站在Java虚拟机的角度来看,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现[1],是虚拟机自身的一部分;另外一种就是其他所有 的类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。

image-20201206211935633

image-20201206211959445

扩展类加载器间接性的继承了ClassLoader,所以扩展类加载器也是其他加载器。并且可以发现,他们是平级关系。。不是一个单独的类文件,而是静态内部类。

  • 启动类加载器(Bootstrap Class Loader)/ 引导类加载器:

    • 启动类加载器是C/C++语言实现的,嵌套在JVM内部。
    • 前面已经介绍过,这个类加载器负责加载存放在 <JAVA_HOME>lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够 识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录中也不会被加载)类 库加载到虚拟机的内存中。
    • 启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时, 如果需要把加载请求委派给引导类加载器去处理,那直接使用null代替即可。
    • 扩展类加载器和应用程序类加载器也是被此加载器加载,并指定为其父类。
    • 出于安全考虑,启动类加载器只加载包名为java、javax、sun等开头的类。
  • 扩展类加载器(Extension Class Loader):

    • 这个类加载器是在类sun.misc.Launcher$ExtClassLoader 中以Java代码的形式实现的。
    • 它负责加载<JAVA_HOME>libext目录中,或者被java.ext.dirs系统变量所 指定的路径中所有的类库。如果用户创建的JAR放在此目录中,也会自动由扩展类加载器加载。
    • 根据“扩展类加载器”这个名称,就可以推断出这是一种Java系统类库的扩 展机制,JDK的开发团队允许用户将具有通用性的类库放置在ext目录里以扩展Java SE的功能,在JDK 9之后,这种扩展机制被模块化带来的天然的扩展能力所取代。
    • 由于扩展类加载器是由Java代码实现 的,开发者可以直接在程序中使用扩展类加载器来加载Class文件。
  • ·应用程序类加载器(Application Class Loader)/系统类加载器:

    • 这个类加载器由 sun.misc.Launcher$AppClassLoader来实现,Java编写。
    • 由于应用程序类加载器是ClassLoader类中的getSystem- ClassLoader()方法的返回值,所以有些场合中也称它为“系统类加载器”。
    • 它负责加载用户类路径 (ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。
    • 如果应用程序中没有 自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
  • 用户自定义类加载器:

    • 为什么要用自定义加载器?

      • 隔离加载类
      • 修改类加载的方式
      • 扩展加载源
      • 防止源码泄露

P34 / 301....

最后修改:2020 年 12 月 06 日 10 : 06 PM
如果觉得我的文章对你有用,请随意赞赏