欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

jvm-01

程序员文章站 2022-12-21 08:54:28
本空间是学习总结,有抄录,也有原创,鞭策自己学习每天需要坚持。...

官网

  • https://docs.oracle.com/javase/8/docs/index.html
  • https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html

编译过程

Person.java -> 词法分析器 -> tokens流 -> 语法分析器 -> 语法树/抽象语法树 -> 语义分析器 -> 注解抽象语法树 -> 字节码生成器 -> Person.class文件

类文件(class文件)

  • 官网The class File Format :https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
  • cafe babe 0000 0034 0027 0a00 0600 1809 0019 001a 0800 1b0a 001c 001d 0700 1e07 001f 0100 046e 616d 6501 0012 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b 0100 0361 6765 0100 0149 0100 0761 6464 7265 ......
  • magic(魔数):
    The  magic  item supplies the magic number identifying the  class  file format; it has the value  0xCAFEBABE . (文件标志)
  • cafe babe

类文件到虚拟机(类加载机制

  • 装载(Load)
    • a-先找到类文件所在位置[(1)通过一个类的全限定名获取定义此类的二进制字节流]
      • 双亲委派
        • 如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。
        • 对于同一个类,如果没有采用相同的类加载器来加载,在调用的时候,会产生意想不到的结果(对于同一个类,如果没有采用相同的类加载器来加载,在调用的时候,会产生意想不到的结果)
        • jvm提供了三种系统加载器:
          • 启动类加载器(Bootstrap ClassLoader):C++实现,在java里无法获取,负责加载<JAVA_HOME>/lib下的类
          • 扩展类加载器(Extension ClassLoader): Java实现,可以在java里获取,负责加载<JAVA_HOME>/lib/ext下的类。
          • 统类加载器/应用程序类加载器(Application ClassLoader):是与我们接触对多的类加载器,我们写的代码默认就是由它来加载,ClassLoader.getSystemClassLoader返回的就是它。
      • 破坏双亲委派
        • 重写loadclass方法?
        • 某些情况下父类加载器需要委托子类加载器去加载class文件。受到加载范围的限制,父类加载器无法加载到需要的文件,以Driver接口为例,由于Driver接口定义在jdk当中的,而其实现由各个数据库的服务商来提供,比如mysql的就写了MySQL Connector,那么问题就来了,DriverManager(也由jdk提供)要加载各个实现了Driver接口的实现类,然后进行管理,但是DriverManager由启动类加载器加载,只能记载JAVA_HOME的lib下文件,而其实现是由服务商提供的,由系统类加载器加载,这个时候就需要启动类加载器来委托子类来加载Driver实现,从而破坏了双亲委派,这里仅仅是举了破坏双亲委派的其中一个情况。
          • 源码:
            • 在调用DriverManager的时候public static <S> ServiceLoader<S> load(Class<S> service) { // 获取当前线程中的上下文类加载器 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
            • public Launcher() { ... try { this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } Thread.currentThread().setContextClassLoader(this.loader); ... }
            • 在Launcher初始化的时候,会获取AppClassLoader,然后将其设置为上下文类加载器,而这个AppClassLoader,就是之前上文提到的系统类加载器Application ClassLoader,所以上下文类加载器默认情况下就是系统加载器。
            • 初始化ServiceLoader,LazyIterator
            • 用AppClassLoader和全限定名来加载实现类try { // 在classpath下查找META-INF/services/java.sql.Driver名字的文件夹 // private static final String PREFIX = "META-INF/services/"; String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); }
    • b-类文件的信息交给jvm[(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构 (方法去Method Area)]
    • c-类文件所对应的对象class—>jvm[(3)在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口(Heap堆)]
  • 链接
    • 验证:保证被加载的类的正确性
      • 文件格式验证
      • 源数据验证
      • 字节码验证
      • 符号引用验证
    • 准备
      • 为类的静态变量分配内存,并将其初始化为默认值
      • 比如 static int a = 10;(这个阶段a还是默认值0)
    • 解释
      • 把类中的符号引用转换为直接引用
      • 地址:String str=地址是什么
  • 初始化
    • 对类的静态变量,静态代码块执行初始化操作
    • 如上面a=10;代码中就会变为10.

运行时数据区(Run-Time Data Areas

  • Method Area(方法区
    • 方法区是各个线程共享的内存区域,在虚拟机启动时创建【逻辑上属于堆的一部分】。会omo
    • (1)方法区在JDK 8中就是Metaspace(元空间 ),在JDK6或7中就是Perm Space(永久代)
    • (2)Run-Time Constant Pool
      Class文件中除了有类的版本、字段、方法、接口等描述 信息外,还有一项信息就是【常量池】,用于存放编译时期生成的各种字面量和符号引用,这部分内容将在 类加载后进 入方法区的运行时常量池中存放。
  • Heap(堆
    • Java堆是Java虚拟机所管理内存中最大的一块,在虚拟机启动时创建,被所有线程共享。 Java对象实例以及数组都在堆上分配。会omo
  • java虚拟机栈 【*Error】
    • 虚拟机栈是一个线程执行的区域,保存着一个线程中方法的调用状态。换句话说,一个Java线程的运行 状态,由一个虚拟机栈来保存,所以虚拟机栈肯定是线程私有的,独有的,随着线程的创建而创建。
      每一个被线程执行的方法,为该栈中的栈帧,即每个方法对应一个栈帧。
      【调用一个方法,就会向栈中压入一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出】
  • 程序计数器
    • 程序计数器占用的内存空间很小,由于Java虚拟机的多线程是通过线程轮流切换,并分配处理器执行时 间的方式来实现的,在任意时刻,一个处理器只会执行一条线程中的指令。因此,为了线程切换后能够 恢复到正确的执行位置,每条线程需要有一个独立的程序计数器(线程私有)。 如果线程正在执行Java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址;
      如果正在执行的是Native方法,则这个计数器为空
  • Native Method Stacks(本地方法栈
    • 如果当前线程执行的方法是Native类型的,这些方法就会在本地方法栈中执行

本文地址:https://blog.csdn.net/zquwei/article/details/107349628

推荐阅读