一起聊聊JVM 虚拟机&&类加载(一)

4 篇文章 0 订阅

虚拟机

虚拟机简介

Java 虚拟机(JVM)是运行java程序的抽象计算机,它是计算机设备的规范,可以采用不同方式进行实现,java 程序通过运行在JVM中实现跨平台,一次编译到处运行,不同的操作系统有不同的JDK版本,通过调用JNI方法去实现调用不同操作系统的方法

编译
加载
打开文件
windows-JDK
linux-JDK
macOS-JDK
java源文件
class二进制字节码
JVM
Java native接口
windows本地方法
linux本地方法
macOS本地方法

Java虚拟机不和包括java的语言绑定,它只和Class文件这种特定的二进制文件格式绑定,class文件中包含了虚拟机指令集和符号表以及若干其他辅信息。

*.java文件
*.rb文件
*.groovy文件
*.class文件
java程序
JRuby程序
Groovy程序
java编译器
JRuby编译器
Groovy编译器
字节码
Java虚拟机

虚拟机产品

  1. Sun HotSpot(JVM)
  2. BEA JRocket
  3. IBM J9
  4. Microsoft JVM
  5. Google Android Dalvik

Class 文件

Class文件是以8位字节基础单位的二进制流,各项数据严格按照顺序排列在class文件中,中间没有任何分隔符,如果超过8位,是按照高位在前的方式存储的。

Class文件的组成

  1. java虚拟机指令集
  2. 符号表
  3. 其他辅助信息

常量池

java代码在编译后,Class文件并表不会保存各个方法,字段在内存里面的最终布局,因为在编译期间并不知道引用类的实际地址,因此只能使用符号描述来代替,当虚拟机真正的运行的时候,需要从class文件的常量池获得对应的符号引用,然后在类的创建或者运行时解析称具体的内存地址

也就是说在类被加载的时候,一定是在内存中分配了具体的地址,这样在解析的时候就可以替换符号引用成真正的地址。

Class文件的常量池主要存放2大类型:

  • 字面量 字面量是接近java语言层面的常量概念,如文本字符串,声明final的常量值等
  • 符号引用 主要包括类和接口的全称限定名,字段的名称和描述符,方法的名称和描述符

虚拟机类加载

类加载到JVM中生命空间

连接
验证Verification
准备Preparation
解析Resolution
加载Loading
初始化Initialization
使用Using
卸载Unloading

加载

加载的过程做的事情是:

  • 获取二进制字节流
  • 静态存储结构转化为方法区的运行时结数据结构
  • 在java堆对象里面生成一个类对象,作为方法区的入口

Java中获取二进制字节流的途径有

  • 从Zip,Jar,War等格式文件中获取
  • 从网络中获取 Applet应用
  • 运行时计算生成,动态代理技术
  • JSP应用生成对应的Class类

验证

验证是连接的第一步,要确保Class文件中的字节流包含的信息符合JVM的要求,验证阶段大体分为如下几个阶段

  • 文件格式验证 验证文件标识是否正确,版本号是否能匹配当前JVM,常量池中的常量是否有不支持的类型等 。经过这个阶段的验证后,字节流才会进入的方法区中进行存储,后面的验证只是基于方法区的存储结构验证,不会在验证字节流
  • 元数据验证 主要是对字节码描述的语义进行分析,保证符号java语言的规范,类的继承,抽象类的实现等等
  • 字节码验证
  • 符号引用验证 该阶段发生在符号引用替换成直接引用时,验证符号引用的匹配校验等等

准备

准备阶段是为了给类分配内存并设置类变量的初始化Clint,这些变量使用的内存,都在方法区中进行分配,只是对类变量进行内存分配(Static 修饰的)不包括实例变量,实例变量是随着类实例化后一起在堆中分配的

:类的初始化 父静态变量 父类静态块 子类静态变量 子类的静态块

: 对象的初始化 父类的变量初始化,父类的构造函数。。。。。。

解析

解析的目的是将常量池中的符号引用替换为直接引用

字段的解析
class A extends B implements C{
    private String str; //字段的解析
}
父类
接口
C是否能匹配
B是否能匹配
结束
抛出异常
结束
A是否能匹配
结束
类方法解析
  • 先查找本类
  • 然后父类中递归查找
  • 在类实现的接口列表及它们的父接口
接口方法解析
  • 先查找本接口
  • 在接口的父接口中递归查找

类加载器

通过类的全限定名来获取描述类的二进制字节流,把类加载这个阶段中的动作放到虚拟机外部去实现,以便应用程序能够自己决定如果去获取自己需要的类,实现这个动作加做类加载器。
Java中有abstract class ClassLoader 类。
ClassLoader.loadClass类的核心总结是:

  • 同一个时间只能允许一个线程去加载类
  • 在加载类之前会检查下是否已经加载过,只有没有被加载的才能去被加载
  • 能父加载器加载的绝不会交给子加载器去加载,为什么要这样,是为了保证安全,比如你自己写个相同名字的java底层类比如java.util.ArrayList通过子加载器到内存中,那不是乱套了么,到时候植入个病毒代码,用的人 不GG了
  • 父加载器加载不到的才会交给子加载器加载

加载器类型

从JVM的角度去看 只存在2中类加载器,一种是启动类加载器(Bootstrap ClassLoader)是由C++语言实现的。还有一种是另外的类加载器,是由java语言实现的,独立于JVM,全部是继承了java.lang.ClassLoader

如果从我们开发角度去看,分为3中类加载器:

  • Bootstrap ClassLoader启动类加载器,负责Java_Home/lib 下面的类库加载到内存中,由于这边涉及到虚拟机本地的实现细节,所有我们开发者无法获取到类加载器的引用。
  • Extension ClassLoader 扩展类加载器 负责加载Java_Home/lib/ext或者由系统变量java.ext.dirs指定位置的类加载到内存中
//Launcher.class文件中
static class ExtClassLoader extends URLClassLoader {
        private static volatile Launcher.ExtClassLoader instance;

        public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
            if (instance == null) {
                Class var0 = Launcher.ExtClassLoader.class;
                synchronized(Launcher.ExtClassLoader.class) {
                    if (instance == null) {
                        instance = createExtClassLoader();
                    }
                }
            }

            return instance;
        }
     ....        
}
  • Application ClassLoader 应用程序类鸡加载器,它负责系统路径ClassPath中指定的类库加载到内存中,这个类加载器是
    ClassLoader类中的getSystemClassLoader静态方法的返回值
 //java/lang/ClassLoader.java 文件中
 public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }
非JVM
AppClassLoader
ExtClassLoader
UseClassLoader1
UseClassLoader2
Bootstrap加载器

有兴趣的可以自己执行看下

String appClassLoaderPath = System.getProperty("java.class.path");
System.out.println(appClassLoaderPath);
String extClassLoaderPath = System.getProperty("java.ext.dirs");
System.out.println(extClassLoaderPath);
String bootClassLoaderPath = System.getProperty("sun.boot.class.path");
System.out.println(bootClassLoaderPath);

双亲委派

什么是双亲委派

  • 如果一个类加载器收到了某个类的加载请求,则该加载器不会加载,而是把这个请求抛给父类加载器,因此所有的类加载请求都会到达顶端的类加载器
  • 只有当父类加载器在其范围内没法找到所需要类,才会把结果反馈给子加载器,子加载器在尝试自己加载

如何打断

自己写个类加载器 并重写loadClass方法 就可以了!

为什么要使用双亲委派

  • 对任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。判断两个类是否"相等",必须是在这两个类被同一个类加载器加载的前提下。
  • 基于双亲委派模型设计,那么Java中基础类,如Object类重复多次的问题就不会存在了,因为经过层层传递,加载请求最终都会被BootstrapClassLoader所响应。加载的Object类也会只有一个,否则如果用户自己编写了一个java.lang.Object类,并把它放到了ClassPath中,会出现很多个Object类,这样Java类型体系中最最基础的行为都无法保证,应用程序也将一片混乱。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值