部分虚拟机笔记

类的生命周期:加载-->验证-->准备-->解析-->初始化-->使用-->卸载。

1.加载的时机:

  • 访问或修改本类的静态变量、调用本类的静态方法或者创建本类对象时。
  • 加载本类时,会先加载父类。
  • 虚拟机会先加载 main 方法所在的类。
  • 存在反射调用时,如果类没有加载,会先进行加载。

只有上述几种方法会主动加载类,其他引用类的方式不会都不会加载类。例如:

  • 通过本类访问父类的静态成员变量时,只会加载父类。不会加载本类。

此外,加载接口时,不会加载父接口。

2.上述每一步操作的内容:

  • 加载:依据类的全限定名,定位到类的字节码文件;将字节码文件变为方法区的类的 class 对象;注:class 对象存放在方法区,而不是堆中。
  • 验证:由于虚拟机可以运行所有符合语法要求的字节码文件,而不仅仅是java 编译器生成的字节码文件,甚至可以是手动构造的字节码文件,而这些字节码文件不一定符合语法要求,所以需要对其进行多种验证,如文件格式验证(验证字节码语法是否正确),元数据验证(验证类的访问权限,方法调用和成员变量的访问权限),字节码验证(类的方法定义是否符合要求)等。虽然在验证阶段会进行访问权限验证,但是 java 的访问权限错是在编译阶段抛出的。java 编译器会进行一系列的错误检查。
  • 准备:为类的静态变量分配内存,并进行默认初始化。这些静态变量内存分配在方法区。
  • 解析:虚拟机将符号引用替换为直接引用的过程,例如方法引用,静态变量和常量的引用等。
  • 初始化:此处为类初始化而不是实例的初始化。
    • 编译器自动收集类中所有静态变量的赋值动作和静态初始化代码块,合成一个 <clinit>() 方法。编译器收集的顺序是由语句在源文件中出现的顺序决定的。静态初始化代码块只能访问定义在该代码块之前的静态变量,定义在它之后的变量,代码块中只能对其赋值,但不能访问。
    • <clinit>() 方法与类的构造函数不同,它不需要显示地调用父类构造器,由于加载本类时,会先加载父类,所以在初始化本类时,父类已经完成初始化。
    • <clinit>() 方法对于类或接口来说不是必须的,当类或接口中没有静态初始化语句或静态初始化代码块时,就不需要 <clinit>() 方法。
    • 多个线程同时初始化一个类时,虚拟机会保证类被正确的加锁、同步。

3.类加载器

类加载器负责将.class文件加载到内存中,并为之生成对应的java.lang.class对象。

在Java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。即:两个类相等,必须满足两个类是由同一个类加载器加载,同时这两个类是相同包下的同一个class文件。否则,即使加载自相同的字节码文件,但类加载器不同,也会生成两个 class 对象。

常用的类加载器:

  • 启动类加载器:负责加载 JAVA_HOME/lib 目录下的类。
  • 扩展类加载器:负责加载 JAVA_HOME/lib/ext 目录下的类。
  • 应用程序类加载器:负责加载用户路径 classPath 上指定的类库。主要是当前工程目录下的类。

双亲委派模型

双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都要有自己的父类加载器。此处类加载器直接的父子关系一般不会以继承的关系实现,而是都使用组合关系来实现。

双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都会传送到顶层的启动类加载器中,只有当父类加载器没有找到所需的类时,子加载器才会尝试加载。

乐观锁:基于CAS实现。

synchronized 和 Lock的区别:一个是底层,一个是包实现,一个可中断,有公平性,有多个条件对象。乐观锁。

并发的三大特性:

  • 原子性
  • 可见性
  • 有序性

锁优化的方法:

  • 适应性自旋
  • 锁消除:基于逃逸分析,分析出一些数据不可能逃逸到其他线程中,即不可能存在竞争时,就消除锁。
  • 锁粗化。
  • 轻量级锁:
原文地址:https://www.cnblogs.com/echie/p/9867883.html