java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Klass模型与类加载

Java中Klass模型与类加载的详细机制

作者:染指1110

这篇文章主要介绍了Java中Klass模型与类加载的详细机制,java语言是在jvm中运行而jvm是不认识java代码的我们使用javac编译的class文件jvm是不认识的 所以有一个类加载的动作 这个动作就是把class字节码拼装成一个klass类型,需要的朋友可以参考下

klass模型

klass模型是jvm中的数据类型,这个数据类型表示的是一个java类

java语言是在jvm中运行而jvm是不认识java代码的,我们使用javac编译的class文件jvm是不认识的,所以有一个类加载的动作,这个动作就是把class字节码拼装成一个klass类型。

这个klass类型是c++中的一个类, klass里面有java类中的所有信息比如它的属性、 方法、 修饰符等成为类的元信息。

这些信息放在元空间中,通过下面的klass类的继承关系图可以看出:

在这里插入图片描述

metaspace 中文意思就是元空间,meatadata中文意思元数据,klass是c++中表示java类的数据类型。

klass下面又分为普通类型与数组类型,普通类型就是我们自己定义的java类一个java类,就是一个instanceKlass这个东西是放在方法区中,然后一个java类的对象就是一个instanceMirrorKlass,它是放在堆中。

而数组类型又分为两种TypeArrayKlass用来表示一个基本数据类型的数组,ObjArrayKlass表示一个引用类型的数组。

数组是一个动态的数据类型,数组会在jvm运行它的那一刻进行创建,否则它没有实体。

类的加载过程

类的加载由五个部分加载、验证、准备、解析、初始化。

加载

就是把硬盘上的class文件加载到内存,然后把它二进制流中的数据读取成一个klass,这时的加载只是加载了java类的信息,不包含属性。

验证

就是验证这个class符不符合要求

准备

这一步把静态变量赋初值

在这里插入图片描述

上面是静态变量的默认值 静态变量会存放在class对象中 然后准备这一步的作用。

如果在java代码中写

static int a;

它不会在clinit中把这个静态变量赋值的操作写进去, clinit是编译后的静态代码块一个java类只会生成一个静态代码块也就是只会生成一次clinit ,生成一次之后只会往clinit中按着顺序往里添加被static修饰的代码。

像这样的写法如果没有准备操作就不会往clinit中添加然后a就会被丢失

static int b = 0;

这样写, clinit中就会有b=0这样的代码, 这个clinit会在初始化的时候调用。

在class加载的时候只加载了类信息属性没有被加载, 而static是类加载的时候就会被创建可以直接用类名点属性名, 看类的加载机制这5步只有在准备与初始化执行静态方法的时候才有赋值的动作, 而初始化执行的静态代码块是在java编译的时候生成。

它生成的机制是静态属性必须有赋值操作, 所以说如果没有准备操作a就会被丢失, 如果在类加载的过程中没有赋值的操作就不会放到klass中, 放不到klass中也就意味着在java类加载到堆中的时候也是什么变量都没有的, 准备这一步操作就是为了可以把静态变量放到klass中。

如果被final修饰,在编译的时候会给属性添加ConstantValue属性,准备阶段直接完成赋值,即没有赋初值这一步。

解析

解析就是把符号引用变为直接引用, 比如java代码编制之后会把 int a = 3 这个3会写到常量池。

常量池会给3分配一个地址, 而符号引用就是a = 3在常量值中的地址,直接引用是指向运行时常量池。

运行时常量池就是把常量池中的3变成了存放3的内存地址, 然后就成了a=3的内存内地这样叫直接引用。

初始化

执行静态代码块, 完成静态变量的赋值, 它是在jvm层面加锁的 。

所以在java中new对象的时候会出现死锁比如:

现在有线程a跟b,

a的run方法中是先让它睡眠1毫秒然后创建b的对象, 就是new b();

b的run方法中是创建a对象就是new a();

然后在main方法中执行a与b的start方法, 先让a执行start()方法 这样就会有很大几率触发创建对象的死锁。

还有如果静态属性的位置有锁变化会导致最终的结果发生变化。

比如

public class A{
	static int a;
	static A a = new A();
	stataic int b = 1;
	public A(){
	   a++;
	   b++;
	}
}

然后在main方法中打印 ,属性a与属性b他们的结果是 1跟1

这样的现象导致的原因就是, 属性a没有赋值就不会生成到clinit中, 然后clinit会根据代码顺序生成 ,也就是new A()操作会在第一个 a是在准备阶段赋初值是0然后b也在准备节点赋初值是0, 然后到了初始化这里, 执行clinit静态代码块首先执行new A()在A的构造方法中进行了a与b的自增这时都是1然后就执行完, 执行完new A() 之后就执行b=1;的赋值操作, 所以就导致了结果是1,1。

执行类的静态代码块 clinit *

1、如果没有静态属性、有静态属性但无赋值操作、静态代码段,生成的字节码文件中就没有clinit方法块

2、final修饰,不会在clinit方法块中体现

3、一个字节码文件只有一个clinit方法块

4、clinit方法块中生成的代码顺序与Java代码的顺序是一致的。这个会影响程序最终结果

到此这篇关于Java中Klass模型与类加载的详细机制的文章就介绍到这了,更多相关Klass模型与类加载内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文