热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JVM笔记(一)类加载子系统

简介jvm是一个应用程序,其作用是运行jvm字节码,为什么叫jvm字节码呢?因为jvm支持运行各种语言编译成的字节码,而不仅仅是java,当然java字节码是最广泛的。其功能主要分




简介

jvm是一个应用程序,其作用是运行jvm字节码,为什么叫jvm字节码呢?因为jvm支持运行各种语言编译成的字节码,而不仅仅是java,当然java字节码是最广泛的。其功能主要分为两点:


  1. write once,run anywhere:在不同的硬件平台都有对应的jvm程序,字节码可以运行在任意平台的jvm中。
  2. 提供内存管理和垃圾回收等功能

JVM支持的指令流是基于栈的指令集架构,下面是将该方法所在类编译成的class文件,通过javap进行反编译后的代码,可以看到基本指令就是iconst,istore,iload等,另一种指令集架构是基于寄存器的指令集架构,代表就是汇编语言。来说说这两者的区别以及为什么JVM采用的基于栈的指令集架构。

public static void main(String[] args) {
int a = 1;
int b = 2;
int c = a + b;
}
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: istore_3
8: return

基于栈式架构


  1. 设计和实现简单,适用于资源受限的系统
  2. 指令集中的指令基本是零地址指令,指令集更小,编译器容易实现
  3. 不需要硬件支持,可移植性更好,更好实现跨平台

基于寄存器架构


  1. 依赖硬件,可移植性差
  2. 性能优秀,执行高效
  3. 花费更少的指令完成一项操作,以1,2,3地址指令 为主。

为了更好的跨平台,Java的指令都是根据栈来设计


发展历程

Sun Classic VM : Java1.0发布,世界上第一款商用Java虚拟机,只提供解释器

Exact VM : jdk1.2时发布,提供了准确式内存管理,热点探测及编译器与解释器混合工作模式,短暂使用,很快就被HotSpot替换

HotSpot:jdk1.3时发布,目前JDK6,8等新版本默认虚拟机都是HotSpot,HotSpot含义就是热点代码探测技术。

JRockit:BEA公司发明,已被oracle收购,专注于服务器端应用,不包含解释器,是世界上最快的JVM。JRockit Real Time(毫秒或微妙级响应)&MissionControl(极低开销来监控,管理和分析生产环境的工具)是其特点。

J9:IBM发布,仅广泛应用于IBM的各种Java产品,移植性较差。

KVM,CDC/CLDC HotSpot:Oracle应用在移动领域的Java虚拟机,已几乎不用;Azul VM:应用在Azul Systems的专有硬件Vega系统上。Liquid VM:运用在BEA公司Hypervisor系统,实现了操作系统的必要功能,可直接与硬件交互,目前已停止。Apache Harmony:IBM和Intel联合开发,但Sun公司不让其获得JCP认证,最终退役。最后被应用在了Android SDK中。MicroSoft JVM:微软用以支持浏览器中的Java程序,只能在windows平台运行,但遭Sun公司指控,被迫抹掉。目前windows上都是HotSpot。

Taobao JVM:阿里基于OpenJDK,深度定制且开源的高性能服务器版Java虚拟机。GCIH技术实现了off-heap:将生命周期长的Java对象从heap中移到了heap之外,且GC不管理此类对象,降低回收频率和提高回收效率,heap之外的对象还能在多个Java虚拟机进程中实现共享。降低JNI开销…


类加载子系统(Classload sub system)

类加载子系统负责从文件系统或者网络中加载Class文件,加载的类信息会存放在方法区的内存空间,除了类的信息外,方法区中还存放运行时常量信息,可能还包括字符串字面量和数字常量。分为三步:加载,链接,初始化。


加载(Loading)

简单来说是指查找字节流,并据此创建类的过程。详细:通过类的全限定名(路径和文件名)获取此类的二进制流,将二进制字节流的静态存储结构转化为方法区的运行时数据结构,在内存中生成一个java.lang.Class对象,作为这个类的数据的访问入口。

上述过程通过**类加载器(ClassLoader)**完成。类加载器分为启动类加载器(bootstrap),扩展类加载器(extention),应用类加载器(application)和自定义加载器。

启动类加载:由C++实现,在Java中用null来指代,只加载最基础最重要的类,例如JRE的lib目录下jar包中的类以及有虚拟机参数-Xbootclasspath指定的类。

扩展类加载器:其父类是启动类加载器,加载相对次要但通用的类,例如JRE的lib/ext目录下jar包的类,以及java.ext.dirs指定的类。(java 9 之后,改名为平台类加载器,加载更多类)

应用类加载器:其父类是扩展类加载器,加载应用程序路径下的类。

自定义加载器:实现特殊的加载方式,例如对class文件加密,加载时利用自定义的类加载器进行解密后加载。

双亲委派模型

JVM对class文件采用按需加载的方式,加载class文件时采用的双亲委派模型,如下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WB59LCZR-1609912271538)(C:\Users\z672916\Desktop\日常文档\pic\类加载.webp)]

当收到类加载请求,会先查找是否加载过,若加载过,直接返回,反之不会自己先去加载,而是把这个请求委托给父类加载器,若父类加载器还存在父类加载器,进一步向上委托,最终可能会到顶层的启动类加载器进行加载。若父类加载器可以完成类加载,则成功返回,若无法完成,则子加载器会尝试加载。看下源码就很清晰了。

protected Class loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先检查这个classsh是否已经加载过了
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// c==null表示没有加载,如果有父类的加载器则让父类加载器加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//如果父类的加载器为空 则说明递归到bootStrapClassloader了
//bootStrapClassloader比较特殊无法通过get获取
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {}
if (c == null) {
//如果bootstrapClassLoader 仍然没有加载过,则递归回来,尝试自己去加载class
long t1 = System.nanoTime();
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

优点:假设攻击者想篡改系统级别的类,例如String.class,但是呢启动类加载器已经加载了String.class对象,便不会再加载篡改后的,保证了一定的安全性;避免重复加载,父加载器加载过的class对象,不会再次加载。

PS:在JVM虚拟机中,类的唯一性由类加载器实例和类的全名一同确定,哪怕相同的class文件,由不同的类加载器加载,也会得到两个不同的类。


链接(LINKING)

链接分为三个步骤,分别是验证,准备,解析


验证(Verify)

确保class文件的字节流中包含的信息符合虚拟机要求,保证被加载类的正确性,不会危害虚拟机。包含四种验证:文件格式验证(所有class文件的开头的都是cafe babe,叫做魔数),元数据验证,字节码验证,符号引用验证。


准备(prepare)

为类变量(以static修饰的变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。


  1. 不会为实例变量分配初始化,而实例变量会随着对象分配在堆中
  2. 设置初始值,会设为默认的零值

为类静态变量分配内存并且设置该类变量的默认初始值,即零值。不包含用static final 修饰的常量,static final在编译时候就分配了。不会为实例变量分配初始化,类静态变量会分配方法区中,而实例变量会随着对象分配在堆中。


解析(Resolve)

将常量池(指的是栈帧中的常量池)内的符号引用转换为直接引用。

解析操作往往伴随JVM执行完初始化之后再执行


初始化(initialize)

执行()方法的过程。

1.此方法是javac编译器自动收集类中所有类变量的赋值操作和静态代码块中的语句合并而来。()方法中的指令按照语句在原文件中出现的顺序执行。不同于构造器方法()方法(类加载完成后,创建对象时候将调用的 ()方法来初始化对象)

public class Test {
static {
// 给变量赋值可以正常编译通过
i = 0;
// 这句编译器会提示"非法向前引用"
System.out.println(i);
}
static int i = 1;
}

2.若该类包含父类,JVM会保证再执行子类()方法前,先调用父类的()方法。

3.JVM会保证在多线程下一个类的()方法同步加锁。


总结

JVM是一个基于栈的指令集架构,运行字节码的应用程序,并且提供了内存管理,垃圾回收等功能。类加载子系统是JVM运行字节码的第一步,将编译后的class文件加载到内存中。其完成流程如下:
在这里插入图片描述



推荐阅读
  • 兆芯X86 CPU架构的演进与现状(国产CPU系列)
    本文详细介绍了兆芯X86 CPU架构的发展历程,从公司成立背景到关键技术授权,再到具体芯片架构的演进,全面解析了兆芯在国产CPU领域的贡献与挑战。 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 线程能否先以安全方式获取对象,再进行非安全发布? ... [详细]
  • 本文介绍了 Go 语言中的高性能、可扩展、轻量级 Web 框架 Echo。Echo 框架简单易用,仅需几行代码即可启动一个高性能 HTTP 服务。 ... [详细]
  • 2020年9月15日,Oracle正式发布了最新的JDK 15版本。本次更新带来了许多新特性,包括隐藏类、EdDSA签名算法、模式匹配、记录类、封闭类和文本块等。 ... [详细]
  • 本文详细介绍了Java反射机制的基本概念、获取Class对象的方法、反射的主要功能及其在实际开发中的应用。通过具体示例,帮助读者更好地理解和使用Java反射。 ... [详细]
  • 本文介绍了在 Java 编程中遇到的一个常见错误:对象无法转换为 long 类型,并提供了详细的解决方案。 ... [详细]
  • 在软件开发过程中,经常需要将多个项目或模块进行集成和调试,尤其是当项目依赖于第三方开源库(如Cordova、CocoaPods)时。本文介绍了如何在Xcode中高效地进行多项目联合调试,分享了一些实用的技巧和最佳实践,帮助开发者解决常见的调试难题,提高开发效率。 ... [详细]
  • 本文详细解析了Java类加载系统的父子委托机制。在Java程序中,.java源代码文件编译后会生成对应的.class字节码文件,这些字节码文件需要通过类加载器(ClassLoader)进行加载。ClassLoader采用双亲委派模型,确保类的加载过程既高效又安全,避免了类的重复加载和潜在的安全风险。该机制在Java虚拟机中扮演着至关重要的角色,确保了类加载的一致性和可靠性。 ... [详细]
  • Java中不同类型的常量池(字符串常量池、Class常量池和运行时常量池)的对比与关联分析
    在研究Java虚拟机的过程中,笔者发现存在多种类型的常量池,包括字符串常量池、Class常量池和运行时常量池。通过查阅CSDN、博客园等相关资料,对这些常量池的特性、用途及其相互关系进行了详细探讨。本文将深入分析这三种常量池的差异与联系,帮助读者更好地理解Java虚拟机的内部机制。 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • 我有一个从C项目编译的.o文件,该文件引用了名为init_static_pool ... [详细]
  • JVM钩子函数的应用场景详解
    本文详细介绍了JVM钩子函数的多种应用场景,包括正常关闭、异常关闭和强制关闭。通过具体示例和代码演示,帮助读者更好地理解和应用这一机制。适合对Java编程和JVM有一定基础的开发者阅读。 ... [详细]
  • 本文总结了Java初学者需要掌握的六大核心知识点,帮助你更好地理解和应用Java编程。无论你是刚刚入门还是希望巩固基础,这些知识点都是必不可少的。 ... [详细]
  • 本文将带你快速了解 SpringMVC 框架的基本使用方法,通过实现一个简单的 Controller 并在浏览器中访问,展示 SpringMVC 的强大与简便。 ... [详细]
author-avatar
Christy-1221
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有