【笔记】JVM之类加载过程

JVM的架构模型

Java编译器输入的指令流基本上是一种基于栈的指令集架构,另外一种指令集架构则是基于寄存器的指令集架构

具体来说,这两种架构之间的区别:

基于栈式架构的特点

  • 设计和实现简单,适用于资源受限的系统;
  • 避开了寄存器的分配难题:使用零地址指令方式分配
  • 指令流中指令大部分是零地址指令,其执行过程依赖于操作栈。指令集更小,编译器容易实现
  • 不需要硬件支持,可移植性更好,更好实现跨平台

基于寄存器架构的特点

  • 典型的应用是x86的二进制指令集:比如传统的pc以及Android的Davlik虚拟机。
  • 指令集架构则完全依赖硬件,可移植性差
  • 性能优秀和执行更高效
  • 花费更少的指令去完成一项操作
  • 在大部分情况下,基于寄存器架构的指令集往往都以一地址指令,二地址指令和三地址指令为主,而基于栈式架构的指令集却是以零地址指令为主

类加载子系统作用

image-20200627083541801

类加载器ClassLoader角色

image-20200627084259375

类的加载过程

image-20200627084324576

image-20200627084407504

加载:(类加载过程中的加载,如上图,不是整个加载流程)

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

补充:加载.class文件的方式

  • 从本地系统中直接加载
  • 通过网络获取,典型场景:Web Applet
  • 从zip压缩包中读取,成为日后jar、war格式的基础
  • 运行时计算生成,使用最多的是:动态代理技术
  • 由其他文件生成,典型场景:JSP应用
  • 从专有数据库中提取.class文件,比较少见
  • 从加密文件中获取,典型的防Class文件被反编译的保护措施

链接:

验证:

Java虚拟机识别的字节码文件/验证字节码文件的标识是cafebabe

image-20200704081022657

image-20200627084957054

准备:

public class HelloApp {
    private static int a = 1;//prepare:a = 0 ---> initial : a = 1


    public static void main(String[] args) {
        System.out.println(a);
    }
}

解析:

对字节码文件进行反编译:

javap -v HelloApp.class

image-20200704082354404

初始化

clinit可以理解为class init的方法

image-20200704092734986

image-20200704083025330

public class ClassInitTest {
   private static int num = 1;

    /**
     * 静态代码块
     */
   static{
       num = 2;
       number = 20;
       System.out.println(num);
       //System.out.println(number);//报错:非法的前向引用。
   }

   private static int number = 10;  //linking之prepare: number = 0 --> initial: 20 --> 10

    public static void main(String[] args) {
        System.out.println(ClassInitTest.num);//2
        System.out.println(ClassInitTest.number);//10
    }
}

:zap: 说明:

number看似声明放在了下面,赋值在声明前了,其实是number在linking阶段已经prepare的时候被赋值为默认初始值0,然后到initialization阶段的时候,number按照源文件出现的顺序执行,先被赋值为20,再被覆盖为10

:zap:注意:

上面的例子不能在static中打印number的原因是非法的前向引用。(只分配了内存空间,没声明变量所以不能调用)

image-20200704100827581

方法区–》元空间–》jdk8 加载到内存

补充知识

JAVA类与对象(六)——实例变量与类变量的区别,实例方法和类方法的区别](https://www.cnblogs.com/scf141592/p/5726347.html)

实例变量

  1. 实例变量声明在一个类中,但在方法、构造方法和语句块之外;
  2. 当一个对象被实例化之后,每个实例变量的值就跟着确定;
  3. 实例变量在对象创建的时候创建,在对象被销毁的时候销毁;
  4. 实例变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息;
  5. 实例变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把实例变量设为私有。通过使用访问修饰符可以使实例变量对子类可见;
  6. 实例变量具有默认值。数值型变量的默认值是0,布尔型变量的默认值是false,引用类型变量的默认值是null。变量的值可以在声明时指定,也可以在构造方法中指定;
  7. 实例变量可以直接通过变量名访问。但在静态方法以及其他类中,就应该使用完全限定名:ObejectReference.VariableName。

类变量(静态变量)

  1. 类变量也称为静态变量,在类中以static关键字声明,但必须在方法构造方法和语句块之外。
  2. 无论一个类创建了多少个对象,类只拥有类变量的一份拷贝。
  3. 静态变量除了被声明为常量外很少使用。常量是指声明为public/private,final和static类型的变量。常量初始化后不可改变。
  4. 静态变量储存在静态存储区。经常被声明为常量,很少单独使用static声明变量。
  5. 静态变量在程序开始时创建,在程序结束时销毁。
  6. 与实例变量具有相似的可见性。但为了对类的使用者可见,大多数静态变量声明为public类型。
  7. 默认值和实例变量相似。数值型变量默认值是0,布尔型默认值是false,引用类型默认值是null。变量的值可以在声明的时候指定,也可以在构造方法中指定。此外,静态变量还可以在静态语句块中初始化。
  8. 静态变量可以通过:ClassName.VariableName的方式访问。
  9. 类变量被声明为public static final类型时,类变量名称必须使用大写字母。如果静态变量不是public和final类型,其命名方式与实例变量以及局部变量的命名方式一致。

关于Java Lambda表达式看这一篇就够了

https://objcoding.com/2019/03/04/lambda/

https://www.runoob.com/java/java8-lambda-expressions.html

import java.util.Comparator;

/**
 * lambda 表达式的语法格式如下:
 *
 * (parameters) -> expression
 * 或
 * (parameters) ->{ statements; }
 * 
 * @Author nilzxq
 * @Date 2020-07-05 15:38
 */
public class Java8Tester {
    final static String salution="hello";
    public static void main(String[] args) {
        Java8Tester tester=new Java8Tester();
        //类型声明
        MathOperation addition=(int a,int b)->a+b;
        // 不用类型声明
        MathOperation subtraction = (a, b) -> a - b;
        //大括号中的返回语句
        MathOperation multiplication=(int a,int b)->{return a*b;};
        //没有大括号及返回语句
        MathOperation division=(int a,int b)->a/b;
        System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
        System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
        System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
        System.out.println("10 / 5 = " + tester.operate(10, 5, division));

        //不用括号
        GreetingService greetingService1=message->System.out.println("Hello"+message);
        //用括号
        GreetingService greetingService2=(message)->System.out.println("Hello"+message);
        greetingService1.sayMessage("world");
        greetingService2.sayMessage("Java8");
        //System.out.println(greetingService1);

        //1.
        GreetingService greetingService3=message -> System.out.println(salution+message);
        greetingService3.sayMessage("lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。");

        //2.直接再lambda表达式中访问外层的局部变量
        final int num=1;
        Converter<Integer,String> converter1=(param)->System.out.println(String.valueOf(param+num));
        converter1.convert(2);

        //3.lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
//        int temp=1;
//        //Variable used in lambda expression should be final or effectively final
//        Converter<Integer,String> converter2=(param)->System.out.println(String.valueOf(param+temp));
//        converter2.convert(2);
//        temp=5;
        //4.在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
//        String first = "";
//        //Variable 'first' is already defined in the scope
//        Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length());  //编译会出错
    }

    interface MathOperation{
        int operation(int a,int b);
    }

    interface GreetingService{
        void sayMessage(String message);
    }

    interface Converter<T1,T2>{
        void convert(int i);
    }

    private int operate(int a, int b, MathOperation mathOperation) {
        return mathOperation.operation(a,b);
    }
}