类文件首先需要经过类加载子系统,进行加载,进类信息等加载到运行时数据区,生成Klass的实例。
在类加载子系统中有以下3个阶段操作(广义上的加载):
如果加载的时候失败了,则不会执行后面的链接等操作。
类加载子系统的作用:
譬如反编译后,会产生常量信息,里面包括常量以及符号引用等:
类加载器ClassLoader的角色,以下面的People.class为例:
通过类信息实例,可以通过new 实例化对象,也可以通过getClassLoader()获取类加载器,也可以通过实例getClass()获取类信息实例。
此处的加载,指的是类加载过程中的第一个阶段(环节),主要工作包括:
java.lang.Class
对象,作为方法区该类的各种数据的访问入口,也就是类信息对象。类的.class文件来源方式包括以下:
链接阶段又分为3个阶段:
初始化,就是执行类的构造器<clinit>()
的过程,注意<clinit>()
是类的构造器,不是对象的。<clinit>()
是初始化类的,就是把类装到JVM里的初始化,不是运行时对象的初始化。
<clinit>()
这个方法不需要显式定义,而是javac
编译器自动收集类中的所有变量的赋值动作,加上静态代码块,合并成的一个方法。
<clinit>()
中代码的顺序和我们在类文件写的顺序一致。
执行子类的<clinit>()
方法之前,JVM会保证先执行其父类的<clinit>()
,默认父类是Object
。
仔细观察上面的代码,会发现,final的属性,即使是static修饰的,在<clinit>()
里面都不会存在,这是为什么呢?
这是因为final修饰的是常量,常量不会在初始化的时候执行赋值!!!常量在编译的时候已经分配了,准备阶段会显示初始化。
如果我们将final去掉,就可以发现,去掉final修饰,字节码就会加上该字段的赋值:(下面的ldc是指常量池的意思,从常量池编号为#6的地方,加载该常量)
虚拟机在初始化的时候,已经保证了类的<clinit>()
方法,即使在多线程的环境下,也只会执行一次,其底层的逻辑就是默认同步加锁了。
【作者简介】:
秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。个人写作方向:Java源码解析,JDBC,Mybatis,Spring,redis,分布式,剑指Offer,LeetCode等,认真写好每一篇文章,不喜欢标题党,不喜欢花里胡哨,大多写系列文章,不能保证我写的都完全正确,但是我保证所写的均经过实践或者查找资料。遗漏或者错误之处,还望指正。
平日时间宝贵,只能使用晚上以及周末时间学习写作,关注我,我们一起成长吧~
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。