java类加载机制
程序编译
这里要从c语言开始举个例子,我们最开始接触的c语言为什么能够执行呢?
首先c语言的运行需要一下的几个步骤
编译---->汇编---->链接三个步骤。这三个步骤主要完成的工作就是将c语言这种人可以解读的字符串转变为机器能够执行的010101的指令码(关于指令码的执行步骤后面介绍)
https://blog.youkuaiyun.com/u014376961/article/details/87989769
我们都知道汇编其实就是已经在操作计算机中的存储部件了,只不过是在程序诞生之初,人为的去写0101指令简直是一种折磨,所以为了提高开发的效率,出现了汇编这种语言,可以理解为01010的助记符(帮助人开发程序用的符号集合)。
程序运行
c也需要加载
c编译好之后,我们执行c代码的时候,c代码也必须加载到内存中,os才可以将对应的指令交由计算机执行。只不过c的加载进来就是01001的机器码了,os可直接运行。这与java不同。
程序运行过程
刚才说了汇编语言是01010指令码的一种助记符,但是最终程序的执行还是要依赖010101的指令码,010101指令码的执行是这样的。
这就要结合冯 · 诺依曼的计算机思想了,计算机的计算部件主要分为计算单元(物理上为运算器)、控制单元(PC指向程序的下一条指令)、存储单元(存储设备,即内存条)。01010代码是不能直接执行的,还需要经过解析,将指令中的 操作、操作数等解析出来后,交给具体的运算器,这才可以将指令执行完毕。那么计算机的01010指令的执行过程为:
大致看下cpu的内部结构:
指令寄存器(IR ),指令译码器( ID )和操作控制器( OC )
步骤:
- 指令指针寄存器即pc,指向下一条指令RAM地址
- 控制器将指令指针寄存器中的指令加载到cpu内部的指令寄存器中
- cpu内部的指令寄存器将01010指令送到指令译码器中进行译码,将01010指令码中对应的操作数和操作解析出来交给操作控制器。
- 操作控制器与运算器交互完成一条指令的执行。
java程序运行
java程序编译之后最终是靠jvm这个虚拟机来执行的,简单来说就是:
- 经过类加载后将程序代码.class中存放的代码加载到内存中,也就是常说的类加载
- 执行具体方法的时候从元数据中取出来对应的code,注意这里的code已经是jvm可以认识的jvm指令集合
- jvm将对应的jvm指令集合再次 解释为 对应的汇编代码
- 汇编代码交由OS完成,具体的过程上面已经解释了
一言以蔽之就是:java执行的过程与c大同小异,都是编译后执行,最大的不同就是c编译后可直接运行,java编译后的代码是只能供jvm处理的 .class,OS是不能运行的,因此java还需要再中间再次解释为汇编才可以(jvm指令集合解释为汇编,这也是为什么jvm存在的原因)。
java8中的meta到底和java7 之前的perm区的区别(暂时还不太清楚)
java8之后的meta的一次fullgc的处理过程
https://blog.youkuaiyun.com/caoyuanyenang/article/details/89714220
class解析
class文件
1class文件是javac程序编译好的字节流,他实际上是一个二进制文件,以KA FE BA BE开头
2class文件的大致结构
这个文件中记录了关于这个类的所有信息,
1. KA FE BA BE
2. JAVA 编译版本(javap -verbose xxx.class)
3. 常量池(字段的字符串名称、string类型的字符串)
4. 类的访问控制,就是这个类是public protected private(最终都是指向常量池的)
5. 然后是字段表,记录了整个类中所有类字段的信息,包括字段的访问控制(public、private),字段的名称(在常量池中就是一个字符串),字段的属性(比如是否是一个final类型的字段)。还有关于方法签名的描述,即方法的访问控制,方法的名称,方法的参数列表(比如数组类型的参数列表就表示为[I,二维数组就表示为[[I,其中I就表示的是int)
6. 然后是方法表,上面的字段表中只有关于方法签名的表述,但是真正存放具体方法的地方是方法表,方法表中与字段表一样,也是包含访问控制,方法名称,方法签名,方法返回值。但是这里的方法表中有一个code的属性,这个属性里面存放的就是真实方法里面的代码,这是区别与字段表最大的不同。
7. 然后是属性表,属性表其实不是作为一个单独的表存在,而是与签名的字段表和方法表配合存在的,因为有一些属性是额外记录在属性表的,字段表和方法表中只有一个指向属性表的指针。
举例
比如方法表中的一个必不可少的属性code就放在对应方法的属性表里面,所以真实的方法表里面是不存放code属性的。程序代码在真正执行的时候需要从方法表的code属性中取出代码,然后jvm执行具体的jvm指令集。
再比如字段表中一个重要的属性就是final属性,比如类中有一个字段为
final String name = "sam"
name这个字段表就会有一个属性表,对应的属性表中就会有一个ConstantValue属性(摘自《深入理解java虚拟机6.3.6方法表集合倒数第二段》),用来执行常量池中的某一常亮,所以编译阶段,jvm就把这个属性给填好了。
class对象
刚才的字节码文件(class文件)描述完毕后,对class里面存放的东西有了一个比较清楚的认识,现在想下刚才类加载的顺序
1根据类的全限定名称确定一个class文件的字节流,其实就是要将这个文件读取进来,通过流的方式
2将这个class文件的二进制流装在到内存中,其实就是将这个流读取到内存中来
3创建二进制流对应的class对象,实际上整个类加载阶段就干了这个一件事情,刚才前两部其实一句话概况就是:
将class文件读进来,然后读进来这个字节码的东西需要有个地方存,那么这个class对象就负责存这些信息,只不过有一些信息class对象中并不直接存放,只存放了meta区域的指针(地址),这也就印证了之前的一句话,使用class对象创建类的实例对象。简单概况就是class对象就是字节码文件的内存反应,只不过有一些内容没有直接放在class对象中,因为这个class对象是放在堆内存中的,meta信息是放在meta区域的,class对象中肯定有指向meta区域的指针存在,但是class对象存放了大多数的类信息和类信息的入口。我们可以通过java公共类Class提供的方法就可以看出来
稍微想一下就知道,为什么这个class对象就能提供这么多的方法呢,这么多年才明白,class对象就是class字节码文件的内存映射,字节码里面有啥这个class对象就能帮你取到啥,所以class其实就是你的java文件里面写的所有的东西,只不过是jvm需要将人写的东西装换为机器能认识的东西,所以需要这个class对象。
class对象到底和class文件有啥关联(no)
之前一直没弄明白类加载的时候在加载阶段过程为
通过类的全限定名称找到一个类的(也就是class文件的二进制流)
将二进制流装载到内存中
创建class对象,作为所有访问类元数据的地址(这句话很重要)
现在解释一下这两者之间的区别: