jvm-类加载

jvm简介

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。 JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。
在这里插入图片描述

编译

那么平时写的.java文件是如何进入jvm当中的?

1、首先通过java编译器将.java文件编译成jvm能识别的.class字节码文件:javac xxx.java
	当然字节码文件也可反编译成二进制文件,再通过:javap xxx.class >xxx.txt将class文件反编译

java文件:Test.java

package com.base.model;
public class Test {
    private String name;
    private static int age;
    private static String sex="male";
    public void run(){
        System.out.println(this.name);
    }
}

编译成class文件:javac Test.java,生成Test.class文件
class文件是二进制格式的
通过javap将class文件反编译:javap Test.class >txt输出到指定文件中
或者通过添加参数-v -p编译成jvm指令文件
在这里插入图片描述

2、通过类加载将class文件加载进jvm中

类加载

class文件----->输入流----->加载进jvm(类加载器)流程

1、虚拟机把Class文件加载到内存
3、并对数据进行校验,转换解析和初始化
3、形成虚拟机可以直接使用的Java类型,即java.lang.Class
装载
1、从本地直接加载
2、通过网络下载.class文件
	场景:Web Applet,也就是我们的小程序应用
3、从zip,jar等归档文件中加载.class文件
	场景:后续演变为jar、war格式
4、将Java源文件动态编译为.class文件,也就是运行时计算而成
	场景:动态代理技术
5、从加密文件中获取
	场景:典型的防Class文件被反编译的保护措施
1、通过一个类的全限定名获取定义此类的二进制字节流**(由上可知,我们不一定从字节码文件
中获得,还有上述很多种方式)
思考:那么这个时候我们是不是需要一个工具,寻找器,来寻找获取我们的二进制字节流。而我们的
java中恰好有这么一段代码模块。可以实现通过类全名来获取此类的二进制字节流这个动作,并且将
这个动作放到放到java虚拟机外部去实现,以便让应用程序决定如何获取所需要的类,实现这个动作
的代码模块成为“类加载器”。
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口

在我们的装载阶段完成之后,这个时候在我们的内存当中,我们的运行时数据区的方法区以及堆就已经有数据了
方法区:类信息,静态变量,常量
堆:代表被加载类的java.lang.Class对象

链接
1、验证:文件格式验证、元数据验证、字节码验证、符号引用验证
2、准备
	为类的静态变量分配内存,并将其初始化为默认值()
	用final修饰的static,因为final在编译的时候就会分配
	这里不会为实例变量(也就是没加static)分配初始化,类变量会分配在方法区中,而实例变量
	是会随着对象一起分配到Java堆中

正常打印出0,因为静态变量i在准备阶段会有默认值0

public class Demo1 {
 private static int i;
 public static void main(String[] args) {
 // 正常打印出0,因为静态变量i在准备阶段会有默认值0
 System.out.println(i);
 }
}

编译通不过,因为局部变量没有赋值不能被使用

public class Demo2 {
 public static void main(String[] args) {
 // 编译通不过,因为局部变量没有赋值不能被使用
 int i;
 System.out.println(i);
 }
}
3、解析:把类中的符号引用转换为直接引用
初始化

初始化阶段是执行类构造器()方法的过程;在准备阶段,类变量已赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的主观计划去初始化类变量和其他资源,比如赋值

在Java中对类变量进行初始值设定有两种方式:
	1、声明类变量是指定初始值
	2、使用静态代码块为类变量指定初始值

按照程序员的逻辑,你必须把静态变量定义在静态代码块的前面。因为两个的执行是会根据代码编写的顺序来决定的,顺序搞错了可能会影响你的业务代码

JVM初始化步骤:
	假如这个类还没有被加载和连接,则程序先加载并链接该类
	假如该类的直接父类还没有被初始化,则先初始化其直接父类
	假如类中有初始化语句,则系统依次执行这些初始化语句
主动初始化:
	创建类的实例,也就是new的方式
	访问某个类或接口的静态变量,或者对该静态变量赋值
	调用类的静态方法
	反射(如 Class.forName(“com.carl.Test”) )
	初始化某个类的子类,则其父类也会被初始化
	Java虚拟机启动时被标明为启动类的类(JvmCaseApplication ),直接使用 java.exe 命令来
	运行某个主类
被动初始化:
	引用父类的静态字段,只会引起父类的初始化,而不会引起子类的初始化。
	定义类数组,不会引起类的初始化。
	引用类的static final常量,不会引起类的初始化(如果只有static修饰,还是会引起该类初始化的)。
卸载

在类使用完之后,如果满足下面的情况,类就会被卸载:

1、该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例
2、加载该类的ClassLoader已经被回收
3、该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方
法

如果以上三个条件全部满足,jvm就会在方法区垃圾回收的时候对类进行卸载,类的卸载过程其实就是在方法区中清空类信息,java类的整个生命周期就结束了。但是一般情况下启动类加载器加载的类不会被卸载,而我们的其他两种基础类型的类加载器只有在极少数情况下才会被卸载

类加载器

在装载(Load)阶段:通过类的全限定名获取其定义的二进制字节流,需要借助类装载器完成,顾名思义,就是用来装载Class文件的

1、负责读取 Java 字节代码,并转换成 java.lang.Class 类的一个实例的代码模块
2、类加载器除了用于加载类外,还可用于确定类在Java虚拟机中的唯一性
	一个类在同一个类加载器中具有唯一性(Uniqueness),而不同类加载器中是允许同名类存在
	的,这里的同名是指全限定名相同。但是在整个JVM里,纵然全限定名相同,若类加载器不
	同,则仍然不算作是同一个类,无法通过 instanceOf 、equals 等方式的校验
类加载器分类
Bootstrap ClassLoader: 负责加载$JAVA_HOME中 jre/lib/rt.jar 里所有的class或Xbootclassoath选项指定的jar包
Extension ClassLoader:负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar 或 -Djava.ext.dirs指定目录下的包
App ClassLoader:负责加载classpath中指定的jar包及 Djava.class.path 所指定目录下的类和jar包
Custom ClassLoader:通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据自身需要自定义的classLoader,如tomcat,jboss
JVM类加载机制的三种特性
1、全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该
类加载器负责载入,除非显示使用另外一个类加载器来载入
2、父类委托,“双亲委派”是指子类加载器如果没有加载过该目标类,就先委托父类加载器加载该目标类,
只有在父类加载器找不到字节码文件的情况下才从自己的类路径中查找并装载目标类;父类委托别名就
叫双亲委派机制。
"双亲委派"机制加载Class的具体过程是:
2.1、ClassLoader先判断该Class是否已加载,如果已加载,则返回Class对象;如果没有则委托
	给父类加载器。
2.2、父类加载器判断是否加载过该Class,如果已加载,则返回Class对象;如果没有则委托给
	祖父类加载器。
2.3、依此类推,直到始祖类加载器(引用类加载器)。
	始祖类加载器判断是否加载过该Class,如果已加载,则返回Class对象;如果没有则尝试
2.4、从其对应的类路径下寻找class字节码文件并载入。如果载入成功,则返回Class对象;
	如果载入失败,则委托给始祖类加载器的子类加载器。
2.5、始祖类加载器的子类加载器尝试从其对应的类路径下寻找class字节码文件并载入。如果载
	入成功,则返回Class对象;如果载入失败,则委托给始祖类加载器的孙类加载器。
2.6、依此类推,直到源ClassLoader。
2.7. 源ClassLoader尝试从其对应的类路径下寻找class字节码文件并载入。如果载入成功,则
   返回Class对象;如果载入失败,源ClassLoader不会再委托其子类加载器,而是抛出常。
    “双亲委派”机制只是Java推荐的机制,并不是强制的机制。
我们可以继承java.lang.ClassLoader类,实现自己的类加载器。如果想保持双亲委派模型,就
应该重写findClass(name)方法;如果想破坏双亲委派模型,可以重写loadClass(name)方法

在这里插入图片描述

3、缓存机制,缓存机制将会保证所有加载过的Class都将在内存中缓存,当程序中需要使用某个
Class时,类加载器先从内存的缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应
的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启
JVM,程序的修改才会生效.对于一个类加载器实例来说,相同全名的类只加载一次,即loadClass
方法不会被重复调用;这也就是我们的类变量为什么只会被初始化一次的由来。
打破双亲委派

双亲委派这个模型并不是强制模型,而且会带来一些些的问题。就比如java.sql.Driver这个东西。JDK只能提供一个规范接口,而不能提供实现。提供实现的是实际的数据库提供商。提供商的库总不能放JDK目录里吧

1、SPI(service Provider Interface) :比如Java从1.6搞出了SPI就是为了优雅的解决这类问题——JDK
提供接口,供应商提供服务。编程人员编码时面向接口编程,然后JDK能够自动找到合适的实现
	Java 在核心类库中定义了许多接口,并且还给出了针对这些接口的调用逻辑,然而并未给出实
	现。开发者要做的就是定制一个实现类,在 META-INF/services 中注册实现类信息,以供核心
	类库使用:创建一个以接口全限定类名为名称,内容是具体实现类的全限定类名。
	比如JDBC中的Driver

执行引擎

javac编译器将Person.java源码文件编译成class文件[我们把这里的编译称为前期编译],交给JVM运行,因为JVM只能认识class字节码
文件。同时在不同的操作系统上安装对应版本的JDK,里面包含了各自屏蔽操作系统底层细节的JVM,这样同一份class文件就能运行
在不同的操作系统平台之上,得益于JVM。这也是Write Once,Run Anywhere的原因所在。
最终JVM需要把字节码指令转换为机器码,可以理解为是0101这样的机器语言,这样才能运行在不同的机器上,那么由字节码转变为
机器码是谁来做的呢?说白了就是谁来执行这些字节码指令的呢?这就是执行引擎

在这里插入图片描述

解释执行:
	Interpreter,解释器逐条把字节码翻译成机器码并执行,跨平台的保证。
	刚开始执行引擎只采用了解释执行的,但是后来发现某些方法或者代码块被调用执行的特别频繁时,就会把这些代码认定为“热点代	码”。
即时编译器:
	Just-In-Time compilation(JIT),即时编译器先将字节码编译成对应平台的可执行文件,运行速度快。即时编译器会把这些热点代码	
	编译成与本地平台关联的机器码,并且进行各层次的优化,保存到内存中。
	热点代码:被多次调用的方法、被多次执行的循环体
JVM采用哪种方式:
	JVM采取的是混合模式,也就是解释+编译的方式,对于大部分不常用的代码,不需要浪费时间将其编译成机器码,只需要用到的时候
	再以解释的方式运行;对于小部分的热点代码,可以采取编译的方式,追求更高的运行效率。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值