当前位置: 首页 > Linux

JVM的类加载器我整理了一下

时间:2023-04-06 02:02:28 Linux

前言之前去面试的时候,面试官问我关于JVM性能调优的问题,因为我在之前的公司项目中没有接触过JVM性能调优的相关问题(感觉这些都是公司架构师考虑的问题),所有的面试官都一头雾水地问我,最后的结果当然都是爽。.,所以,为了查漏补缺,我去学习了一下JVM的相关知识,希望对大家有所帮助。正文在学习任何新知识之前,我都会先列一个学习大纲,然后按照这个学习大纲一步一步的去学习和理解,所以学习JVM这个新技术,我也是分为3个部分。学习:JVM类加载器、JVM内存结构、JVM垃圾回收三个部分要学习。今天的文章是关于JVM类加载器的。一、什么是JVM既然是学习JVM的相关理论知识,那当然要知道什么是JVM。JVM是Java虚拟机(JavaVirtualMachine)的缩写。说到虚拟机,可能有人会问什么是虚拟机。我把虚拟机的相关概念放在这里:它用于执行一系列计算机指令。虚拟机可以分为系统虚拟机和程序虚拟机。系统虚拟机:如VMware,它们完全是物理计算机的仿真,提供了一个可以运行完整操作系统的软件平台。程序虚拟机:如Java虚拟机,专门用来执行单个计算机程序。在Java虚拟机中执行的指令称为Java字节码指令。(JVM运行在操作系统之上,它与硬件没有直接的交互)所以根据定义,我们可以知道JVM是一个程序虚拟机。那么JVM在哪里呢?其实我们在初学Java的时候,一定要根据Java的运行环境,从网上下载JDK安装包。安装完成后,安装路径下会有两个文件夹,一个叫Jdk,一个叫jre,java虚拟机就在jre的文件夹里。它的存在是有原因的,那什么是利用JVM的存在?他有什么用?学过JAVA的人都知道,要运行java程序,首先要将Java源程序(.java)编译成平台无关的字节码文件(.class),然后将字节码文件解释成机器码,跑步。并且讲解的过程是通过Java虚拟机执行的(可以参考下图理解)。java虚拟机就是对字节码文件进行解释,解释的过程其实是一个非常复杂的过程,所以这就是我们今天要讲的话题。2.类加载(classLoading)我们先来了解一下类加载的整个过程。从下图可以看出一个类的生命周期分为5个阶段,加载、连接(包括验证、准备和分析)、初始化、使用(类实例化)、卸载(垃圾收集)。在Java代码,我们都知道一个类(指的是类本身,如Interface、Enum)的加载、连接、初始化等过程都是在程序运行过程中完成的。先说类的加载、连接和初始化。类加载:最常见的情况是从磁盘中加载一个已经存在的类的Class文件(也就是字节码文件)到内存中,放到运行时数据区的方法区中,然后创建一个java.lang.Class对象在内存中封装类的方法区的数据结构类的连接(分为三个阶段):1.验证:保证加载类的正确性2.准备:为类的静态变量(也叫类变量)分配内存,并初始化为默认值(例如int的默认值为0)3.解决:将类中的符号引用转为直接引用给类初始化:给类的静态变量赋值(从代码中自上而下执行)。Java程序可以通过两种方式使用该类:主动使用和被动使用所有Java虚拟机实现。每个类或接口都由Java实现,它们在程序“首次主动使用”时被初始化。重要的是要记住,该类在首次使用时被初始化。如果主动使用它的类或者接口导致初始化(此时的初始化就是加载和连接(连接的三个步骤,注意此时的连接只是完成类的静态变量分配,并初始化为默认值))已完成)我在这里总结了7种活跃的用法:-创建类的实例-访问类或接口的静态变量,或为静态变量赋值-调用类的静态方法-反射(如class.forName())——初始化一个类的子类——Java虚拟机启动时指示为启动类的类——JDK1.7开始完善的动态语言支持;除了以上7种情况,其他的Java类方法都被认为是类的被动使用,不会导致类的初始化。3、类加载连接初始化详解其实我们知道,类加载的最终产物是位于内存中的Class对象。Class对象将类的数据结构封装在方法区中,为Java程序员提供对方法区的访问。内部数据结构的接口。根据上面的总结,我们知道类的连接其实就是在类被加载的时候,就进入了连接阶段。链接就是将已经读入内存的类的二进制数据合并到虚拟机的运行环境中。那么类的验证内容有哪些呢?类文件的结构检查字节码验证的语义检查二进制兼容性验证4.类加载器类加载实际上是由类加载器完成的。我们可以把类加载器想象成帮助JVM工作的小人。那么类加载器的定义是什么,这里根据我个人的理解做一个总结:必须由类加载器加载)。从JDK1.2版本开始,类加载过程采用双亲委托机制。这种机制可以更好的保证Java平台的安全。在这种委托机制中,除了Java虚拟机自带的根类加载器(因为根类加载器本身没有父加载器)外,其他类加载器只有一个父加载器。当Java程序请求加载器loader1加载Sample类时,loader1首先委托其父加载器加载Sample类。如果父加载器能够加载,则父加载器完成加载任务,否则加载器loader1自己加载Sample类。种类。类加载器分为两种:1.Java虚拟机自带的加载器根类加载器(BootstrapClassLoader,又称启动类加载器AppClassLoader)2.用户自定义类加载器java.lang的子类。ClassLoader(所有自定义类加载器都应该继承抽象类ClassLoader类)用户可以自定义类的加载方式,类加载器不需要等到“第一次主动使用”时加载某个类》5.类加载器的双亲委托机制详解本节我们来详细了解一下类加载器的双亲委托机制。父委托机制又叫双亲委托机制(我个人不得不理解其实应该叫父委托机制,因为在源码中是parent而不是parents):在父委托机制中,每个loader形式一个熟悉的结构,按照父子关系(逻辑上,如下图),除了启动类加载器,其他类加载器只有一个父加载器。下面这几种loader,表面上看是继承关系,实际上是包含关系。下面我举个例子来看看父委托机制的实际实现:对于上图的执行过程,不得不详细解释一下类加载器的父委托机制是如何实现的。执行:首先,loader1和loader2是我们自定义的加载器。Loader1尝试加载Sample类。根据父亲的委托机制,loader1并没有直接将Sample类加载到虚拟机中。而是将加载任务传递给系统类加载器完成,系统类加载器再将这个加载任务传递给扩展类加载器,再由扩展类加载器传递给根类加载器完成,因为根类加载器已经是类加载器系统层次结构的最顶层,所以根类加载器会尝试将Sample类加载到虚拟机中(此时根类加载器无法加载它,因为它是从特定目录加载的),由于根类加载器无法完成加载,他会把这个任务交还给扩展类加载器(同样原则上是不能加载的),然后让系统类加载器加载(一般都能加载成功)。最后将这个过程返回给loader1,宣告类加载过程结束。6.获取类加载器的几种方式了解了类加载器的种类之后,我们还需要知道如何获取类加载器。我总结了4种获取类加载器的方式:第一种:获取当前类的ClassLoader:clazz.getClassLoder()具体实现如下:Classclazz1=Class.forName("java.lang.String");System.out.println(clazz1.getClassLoader());第二种:获取当前线程上下文的ClassLoader:Thread.currentThread().getContextClassLoader();具体实现如下:ClassLoadercontextClassLoader=Thread.currentThread().getContextClassLoader();System.out.println(contextClassLoader);第三种:获取系统ClassLoader:ClassLoader.getSystemClassLoader();第四种:获取调用者的ClassLoaderDriverManager.getCallerLoader()我们还需要知道,数组实际上并不是类加载器创建的,而是在需要的时候,由jvm运行时自动创建的。对于一个数组,它的类加载器和它的元素类型的类加载器是一样的。如果元素类型是基本类型,则数组没有类LoaderClassLoader类本身默认是支持并行的。如果子类要支持并行加载,需要自己注册。如果自定义加载器需要并行加载,需要自己配置,通过调用registerAsParallelCapable()7.总结通过上面的相关总结,其实可以发现JVM学习不像spring,springcloud是一个应用框架,立马做事,立马看到效果。JVM不是这样的,它涉及到很多理论。很多同学可能觉得不重要,觉得没什么可学的。其实不一样。就像练武一样。内功修好了,练其他招式就容易了,而且会精益求精,而JVM就相当于内功,所以可以想见,对于JVM的学习显然是非常重要的。以上是我对JVM类加载器的总结。下一篇文章应该是结合java源码对类加载器的理解。最后,最近有很多朋友找我要一份Linux学习路线图,所以我结合自己的经验,利用业余时间熬夜一个月,整理了一本电子书。无论你是面试还是自我提升,相信都会对你有所帮助!免费送给大家,只求大家给我点个赞!电子书|LinuxDevelopmentLearningRoadmap也希望有小伙伴可以和我一起把这本电子书做得更完美!获得?希望老铁们来个三连击,让更多人看到这篇文章。推荐阅读:干货|程序员和高级架构师免费发送工件的必备资源|支持搜索的资源网站