c++单例类导出 找不到符号_《深入理解Java虚拟机》- Java虚拟机是如何加载Java类的?...

e4ae8fbb9d39f1e65e8d8a490c20ad9e.png

欢迎关注头条号:Java小野猫

Java虚拟机是如何加载Java类的? 这个问题也就是面试常问到的Java类加载机制。在年初面试百战之后,菜鸟喜鹊也是能把这流程倒背如流啊!但是,也只是字面上的背诵,根本就是像上学时背书考试一样。

tonight ! 我们把它映射到实战里,看看如何用代码说明这个流程。

ready! go! ----------------在这之前还是搞点理论吧,不然又要先去百度加载机制流程了。

一、类加载机制(理论部分)

类加载机制有三大过程:加载、链接、初始化。其中链接又细分为验证、准备及解析。

Java语言的类型分为两大类:基本类型和引用类型。Java的基本类型是由Java虚拟机预先定义好的。而引用类型又分为:数组类、类、接口、泛型参数。在JVM中,只存在数组类、类、接口三类,而数组类是直接由Java虚拟机直接生成的,其他两类则有字节流而来。

字节流又是怎么来呢?最常见的还是从字节码文件而来(还可以从网络等而来)。所以,我就以字节码文件分析一下加载机制。

1.加载

加载是指查找字节流,并据此创建类的过程。对于数组类来说,它们是可以由Java虚拟机直接生成。而其他两类,却需要借助类加载器,Java虚拟机才能完成查找字节流的工作。

类加载器与类的关系就像是UI与效果图一样,需要将业务需求翻译成效果图。美丽的UI就是类加载器,效果图就是类。

类加载器有一个模型:双亲委派

双亲委派模型是每次收到类加载请求时,先将请求委派给父类加载器完成,如果父类加载器无法完成加载,那么子类尝试自己加载

类加载器分为三种,启动类加载器、扩展类加载器、应用类加载器

其中,

启动类加载器(Application ClassLoader)是负责加载最为基础、最为重要的类,即加载lib目录下核心库

扩展类加载器的父类加载器是启动类加载器,扩展类加载器负责加载相对次要、但又通用的类,即加载libext目录下扩展包

应用类加载器的父类加载器是扩展类加载器,应用类加载器负责加载应用程序路径下的类,即用户路径(classpath)上指定的类库

这三者类加载器的关系可以这样描述:

每当类加载器接收到加载请求时,它会先将请求转发给父类加载器,在父类加载器中没有找到需要的类的情况下,该加载器才会尝试去加载。

比如,应用加载器接收到加载请求, 会先去找父类加载器(扩展类加载器)里有没有需要的类,但该扩展类加载器顶头还有父类加载器(启动类加载器), 所以还要去启动类加载器中查找是否存在需要的类,如果不存在,那扩展类加载器就会自己加载需要的类,然后把引用传递给应用加载器。

类加载器还有一个功能,就是提供命名空间的作用。

在Java虚拟机中,类的唯一性是由类加载器实例以及类的全名一同确定的。即便是同一串字节流,经过不同的类加载器加载,也会得到两个不同的类。(这里可以读者自己实现一下,用两个扩展类加载器加载同一个类,然后打印一下类信息就可以验证了)

2.链接

链接:将创建成的类合并至Java虚拟机中,使之能够执行的过程。(这里是下面代码要验证一个步骤)

链接分为验证、准备、解析。

验证的目的是为了让被加载的类能够满足Java虚拟机的约束。(关于约束条件,今天暂时不讲。)

准备的目的是为了被加载类的静态字段分配内存。(仅仅是分配内存,并没有在内存中写东西)除此之外,有些Java虚拟机还会用来实现虚方法的动态绑定的方法表。

下一步就是我们链接的重要步骤--解析

对于一个方法的调用,编译器会解析一个符号,这个符号能够无歧义地定位到具体的目标上。这个符号包含目标方法所在类的名字、目标方法的名字、接收的参数类型以及返回值类型的符号引用。

如果符号引用指向一个未被加载的类、字段、方法,那么这个解析将触发加载功能。

3.初始化

初始化:为标记为常量值的字段赋值,以及执行方法的过程。

只有初始化完成之后,类才正式成为可执行的状态。

那初始化这个步骤是什么时候执行呢?

JVM规范枚举了下述多种触发初始化的情况:

7e63329f6df4affaedabf35fba514dd1.png

理论部分讲解完了。(参考链接:https://time.geekbang.org/column/article/11523)

二、类加载机制(Linux查看字节码)

在这,主要是因为网上都是一大串的理论描述,看得也是半信半疑。还不如拿出代码来,让自己也心服口服了。

talk is cheap,show the code!

1.classloader

4798058d26802780c869e56040f6d7b4.png

输出如下:

3503d5b4f22743a42d1535161ecb5803.png

结果很惊人,为什么父类加载器的父类加载器(我自己取名为爷爷类加载器。。),照理论来说,应该打印的是bootstrapClassLoader。 其实是它隐身了。贴一下代码里的一段注释:

* Returns the parent class loader for delegation. Some implementations may* use null to represent the bootstrap class loader. This method* will return null in such implementations if this class loader's* parent is the bootstrap class loader.

翻译黄色的句子就是:如果父类加载器是启动类加载器,则将返回null

但还是没解决怎么找bootstrapClassLoader啊。

它实际上不是 java.lang.ClassLoader的子类,而是由JVM自身实现的,我们可以通过这个方法得到实现的类路径。

URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();

2.链接

3.初始化

1.编译的单例类:Singleton.java

2884bf18d7b2a3720261e311175e8ac4.gif
public class Singleton{ private Singleton(){} private static class LazyHolder{ static final Singleton INSTANCE = new Singleton(); static{ System.out.println("LazyHolder."); } } public static Object getInstance(boolean flag){ if(flag) return new LazyHolder[2]; return LazyHolder.INSTANCE; } public static void main(String... args){ System.out.println("---"); getInstance(true); System.out.println("---"); getInstance(false); }}
2884bf18d7b2a3720261e311175e8ac4.gif

2.编译并运行

[root@localhost tmp3]# javac Singleton.java [root@localhost tmp3]# java Singleton------LazyHolder.

3.查看字节码文件(用的新指令:java -verbose:class Singleton)

f705cda25dd7e734c27c5c43479ff098.png

结合2,3的输出,我想,我该讲点啥。

在两段“-----”中,如果没有打印加载信息,我们就以为是连续输出了。但是并没有。根据加载信息来看,中间还会加载Singleton$LazyHolder这个内部类,而这一步,对应的就是getInstance(true);。说明了啥?说明了前面理论部分的链接的最后一句话:

如果符号引用指向一个未被加载的类、字段、方法,那么这个解析将触发加载功能。

这里注意的是,仅仅调用了加载功能而已,这里只完成了创建类而已,并没有让它继续进行链接和初始化功能。所以在这里,也就不会出现打印“LazyHolder.”的字样。

当使用到了getInstance(true)的时候,由于需要使用到“LazyHolder.INSTANCE;”这个静态常量,而它又与new 构造器方法相连通,此时就满足了理论中初始化的解释:

2.当遇到用以新建目标类实例的new指令时,初始化new指令的目标类3.当遇到访问静态字段的指令时,初始化该静态字段所在的类

在这,大家应该也有疑问:getInstance(false)的时候也有new啊。但是,false时候的new,只是new数组,数组里装的只是加载时候生成的类引用。

大家还可以看下面class字节码就知道,注释那里就标记着很清楚是类引用而已。

Singleton.class

2884bf18d7b2a3720261e311175e8ac4.gif
 public static java.lang.Object getInstance(boolean); descriptor: (Z)Ljava/lang/Object; flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=1 0: iload_0 1: ifeq 9 4: iconst_2 5: anewarray #3 // class Singleton$LazyHolder 8: areturn 9: getstatic #4 // Field Singleton$LazyHolder.INSTANCE:LSingleton; 12: areturn LineNumberTable: line 11: 0 line 12: 9 StackMapTable: number_of_entries = 1 frame_type = 9 /* same */ public static void main(java.lang.String...); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS Code: stack=2, locals=1, args_size=1 0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #6 // String --- 5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: iconst_1 9: invokestatic #8 // Method getInstance:(Z)Ljava/lang/Object; 12: pop 13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 16: ldc #6 // String --- 18: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 21: iconst_0 22: invokestatic #8 // Method getInstance:(Z)Ljava/lang/Object; 25: pop 26: return LineNumberTable: line 15: 0 line 16: 8 line 17: 13 line 18: 21 line 19: 26 Singleton(Singleton$1); descriptor: (LSingleton$1;)V flags: ACC_SYNTHETIC Code: stack=1, locals=2, args_size=2 0: aload_0 1: invokespecial #1 // Method "":()V 4: return LineNumberTable: line 1: 0}
2884bf18d7b2a3720261e311175e8ac4.gif

可以看到,在getInstance中,那么#3 和#4 对应的是啥

332c96b5b63cba72d8c939da1e59b424.png

欢迎做Java的朋友们私信我【资料】免费获取免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)

其中覆盖了互联网的方方面面,期间碰到各种产品各种场景下的各种问题,很值得大家借鉴和学习,扩展自己的技术广度和知识面。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值