您的位置:首页 > 其它

类加载机制运行流程

2019-05-23 16:50 477 查看
[code]有没有人听过classloader的,我觉得还是比较重要,几天会写一个案例,你们想想我们最开始学JVM的时候,垃圾回收机制,算法,

参数调优,今天讲到类加载器,把类加载器的所有知识全部讲完,字节码技术在正常的开发是用不到的,但是为什么讲呢,

像代理技术里面,CGLIB的时候就可以把字节码谈出来,你这个人研究比较深了,CGLIB的底层原理,它是一个类库,是做代理技术的,

解码的一个技术,类加载器,加载类的,应用在一些热部署的地方,今天写一个操作,实战操作热部署,你们可能之前是在TOMCAT里面

配一下,我不需要重新发版服务器,今天实际底层的去操作一把,这样聊的比较深的情况下,只要一讨论热部署我就知道,

classloader重新加载一下字节码,他一般是对字节码文件做修改和新增的,类加载器是覆盖你原有的,所以性能不一样,

很少通过字节码实现,一般是通过classloader,加载字节码文件
[code]类加载器可能有很多人已经听说过的,可能前面听起来会比较痛苦,我们直接讲代码可能比较好理解一点,因为都是概念,

好多都是概念,正常我们的一个JAVA代码是怎么来的,JAVA代码是怎么运行的,它是一开始要通过.java文件,然后编译成.class

文件,然后JVM执行class文件,这个是我相信你们学JAVA的时候一定学过的,编码后会转成虚拟机的一个指令,它会把文件加载

到虚拟机的内存里面去,这个过程我们就可以成为类加载过程,为什么类加载的过程比较重要,尤其你们会发现在生产环境下面,

我突然发现我的JAVA代码有bug了,我不想重启服务器,那怎么办呢,我们可以通过热部署技术,不需要通过重新发版服务器,

字节码二进制文件,你需要学C语言,需要学汇编的,我们没有必要研究那么底层,主要是用JAVA写代码,其实不要把类加载器

想的高大上,你们就可以这理解,就相当于JVM,执行你的class文件,从JAVA源代码编译后的文件进行执行,在这个类加载过程中,

可能会比较麻烦,比如最基本的,加载过程,表示读取过程,类加载的过程可能比较广泛,字节码文件不一定是在硬盘上的,网络

都是可以的,当读取完之后,他就会有几个过程,类加载主要是有几个过程呢,简单是三步,加载,然后链接,然后再是初始化,

你们把这三步说完之后,加载表示读取字节码文件,链接里面有验证,准备,解析,解析完了之后最后才会到初始化过程,

我相信很多人在初学的时候,这里面怎么这么麻烦呢,大家只要知道什么呢,这个过程没什么意思,知道他是干嘛就行了,

这个时候我就大体的去说一下,首先第一步是类加载过程,就是把class字节码文件加载到内存中去,class文件是需要源代码

编译后,我们写的都是源代码,JAVA代码,这个时候他们会把class文件读取到内存中去后,那么他会进行一个转换,

他会把这个class文件放在一个方法区中,进行运行,我之前讲过了,方法区里面主要是放静态的,class文件的一些信息,

都会放到方法区里面的,然后他会在堆里生成一个CLASS的对象,这就是我们作为方法区访问的一个入口的时候,

你们记住就行了,系统运行的时候,你去加载这样的class文件的时候,它是二进制文件,我们外部读取的时候也比较多,

有光盘,有硬盘,记住,也有网络读取class文件,这都是可以的,你们下去做个案例,我怎么通过网络,读取字节码文件,

我们主要将后面的代码,整个类加载的过程是怎么样的,相当于你加载的时候,加载完了之后运行在方法区里面之后,

它会在堆内存里面,比如你们有一个对象创建好了之后,new的时候,对象是存放在堆的,引用是存在栈的,这个你们一定

要记住的,然后有一个封装的数据结构,把这个类加载过程大概的说一下

[code]类加载器
类加载的机制的层次结构
每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,
”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的”.class”文件,
并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载,这里我们需要了解一下类加载的过程,如下:
Jvm执行class文件
[code]步骤一、类加载机制
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的
java.lang.Class对象,作为方法区类数据的访问入口,这个过程需要类加载器参与。
当系统运行时,类加载器将.class文件的二进制数据从外部存储器(如光盘,硬盘)调入内存中,
CPU再从内存中读取指令和数据进行运算,并将运算结果存入内存中。内存在该过程中充当着"二传手"的作用,
通俗的讲,如果没有内存,类加载器从外部存储设备调入.class文件二进制数据直接给CPU处理,而由于CPU的
处理速度远远大于调入数据的速度,容易造成数据的脱节,所以需要内存起缓冲作用。
类将.class文件加载至运行时的方法区后,会在堆中创建一个Java.lang.Class对象,用来封装类位于
方法区内的数据结构,该Class对象是在加载类的过程中创建的,每个类都对应有一个Class类型的对象,
Class类的构造方法是私有的,只有JVM能够创建。因此Class对象是反射的入口,使用该对象就可以获得
目标类所关联的.class文件中具体的数据结构。

[code]类加载,字节码文件,类加载器,记载完了之后,然后他会在方法区里面去运行这个数据的,这边我跟你说一下,

这个有好多人有误区的,首先讲一下,我们之前说过的,我们再把内存结构说一下,这个时候我们假设把它叫做栈,

我再copy几份,叫做方法区,我们叫堆,其实我还是讲一下,有的地方又把方法区叫做非堆,我们就把它叫做方法区,

我来问一下你们,我们这里叫做方法区,现在比如说我有一个对象,一加载出来的时候,比如有一个new User()这个对象,

我又让你接受,User user = new User(),那你们说一下,这个user对象是存放在堆呢还是存放在栈呢,你们说一下,

记住这是引用,这是很多人有误区的,在这边我说一下,这是一个误区,以为user是存放在堆里面,记住引用肯定是存放在

栈里面,它是一个局部变量要记住了,他不是全局的,不是静态的,所以在这边我给你说一下,首先这个方法区,我知道很多人

会答错的,确实很正常,这个肯定是存放在栈里面的,首先方法区里是class文件加载信息,new它会存放在堆里面,但是你引用它是存放

在栈里面的,记住,引用是在栈里面的,方法区存放静态数据,我现在方法区只说两个,我现在写一个方法,public void add(){}

我在这个方法里面写一个User user = new User(),这个时候实际上他在栈里面,user它是引用到new User()这个对象,user它是

局部变量,他不是全局的,也不是静态的,所以这个user就存放在栈里面,引用到堆里面,当你一new的时候,这new的这个对象,

new的这个对象会存放在堆里面的,我们不是在说内存结构,new出来的一个User对象,相当于你局部变量去引用的时候,

user是局部变量,他只要栈区的user引用到堆区的new User(),所以这是很多人的误区,说我的user对象是不是存放在堆里面,

user没有在堆里面,因为你是引用堆里的对象,user是局部变量的,栈里面会存放基本的局部变量的信息

[code]类加载的最终产物就是位于堆中的Class对象(注意不是目标类对象),该对象封装了类在方法区
3ff7
中的数据结构,

并且向用户提供了访问方法区数据结构的接口,即Java反射的接口。
[code]还有一个叫做连接的过程,连接的过程我大体的说一下,他这里又三个步骤,验证,准备,解析,你们大体的看一下,

首先认证干嘛呢,确保加载的类信息符合JVM规范,没有安全性方面的问题,正是为类变量,静态变量分配内存并设置类初始化值的阶段,

将内存在方法区里面进行分配,解析就是虚拟机常量池的符号引用替换字节引用过程,就这三个步骤,你们如果想把特别底层搞熟的话,

你一定要学C语言,要学汇编的,因为它这里面特别底层,所以你们现在想学的话,想研究的特别深的话,你们要学JNI技术,这个真的是这

样的,因为之前做安卓的时候,把C语言学会了,直接建立一个native方法,JAVA直接操作C语言,不用走JAVA API了,特别方便的,

有的方法是通过native进行修饰的,内部语言调用外部语言,JNI就是JAVA操作C语言里面的,这个你可以不懂没关系,你只要大概懂就行了,

JNI不是JAVA后端搞的,一般你要做安卓的时候,JNI你是必须要会的,它是要和安卓底层打交道的,然后说一下初始化过程,我之前也讲过的,

走类的构造函数,编译器会自动收藏类中所有类变量的赋值动作和静态语句块,合并在一起进行执行,是根据你的先后过程,当你初始化一个类

的时候,如果父类还没有进行初始化的时候,先触发父类的初始化,这个只要稍微学过JAVASE的都知道这个概念是这样的,最后大体说一下,

在这里写了一个比较简单的例子,你们可以看看这个例子,就是写一个静态代码块,给你们演示一个例子,通过代码可能会比较好理解,

前面都是理论知识,可能确实有点绕
[code]步骤二、连接过程
将java类的二进制代码合并到JVM的运行状态之中的过程
验证:确保加载的类信息符合JVM规范,没有安全方面的问题
准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配
解析:虚拟机常量池的符号引用替换为字节引用过程
步骤三、初始化
初始化阶段是执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译器自动收藏类中的
所有类变量的赋值动作和静态语句块(static块)中的语句合并产生,代码从上往下执行。
当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
虚拟机会保、、、证一个类的<clinit>()方法在多线程环境中被正确加锁和同步
当范围一个Java类的静态域时,只有真正声名这个域的类才会被初始化

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: