各位老铁们好,相信很多人对《JVM虚拟机核心原理深度解析》读书心得分享都不是特别的了解,因此呢,今天就来为大家分享下关于《JVM虚拟机核心原理深度解析》读书心得分享以及的问题知识,还望可以帮助大家,解决大家的一些困惑,下面一起来看看吧!
Java虚拟机设计团队有意在Java虚拟机外部的类加载阶段实现“通过类的完全限定名获取描述类的二进制字节流”的动作,以便应用程序可以决定如何去做。获取所需的类。实现此操作的代码称为“类加载器”。
内存:类加载阶段通过这个动作,使得类加载器虽然只是用来实现类的加载动作,但它在Java程序中的作用远远超出了类加载阶段:
对于任何一个类,加载它的类加载器和类本身必须共同建立它在Java虚拟机中的唯一性。每个类加载器都有一个独立的类名称空间。
这句话可以更通俗地表达:
仅当两个类由同一类加载器加载时,比较两个类是否“相等”才有意义。否则,即使两个类源自同一个Class文件,由同一个Java虚拟机加载,只要加载它们的类加载器不同,这两个类就一定不相等。
java中类平等的体现:
这里所说的“相等”包括代表该类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果。
它还包括各种情况,例如使用instanceof关键字来确定对象所有权。
如果没有注意到类加载器的影响,在某些情况下可能会产生令人困惑的结果。下面的代码演示了不同的类加载器对instanceof关键字操作结果的影响。
/*
* 类加载器和instanceof关键字的演示
*
* @作者zzm
*/
公共类类加载器测试{
公共静态无效主(字符串[] args)抛出异常{
//自定义加载器
类加载器myLoader=new ClassLoader() {
@覆盖
public Class?loadClass(String name) 抛出ClassNotFoundException {
尝试{
String fileName=name.substring(name.lastIndexOf(".") + 1)+".class";
输入流=getClass().getResourceAsStream(fileName);
if (is==null) {
返回super.loadClass(name);
}
byte[] b=new byte[is.available()];
is.read(b);
return DefineClass(name, b, 0, b.length);
} catch (IOException e) {
抛出新的ClassNotFoundException(名称);
}
}
};
对象obj=myLoader.loadClass("org.fenixsoft.classloading.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj实例org.fenixsoft.classloading.ClassLoaderTest);
}
}输出
类org.fenixsoft.classloading.ClassLoaderTest
false 在这两行输出中,从第一行可以看出,这个对象确实是从类org.fenixsoft.classloading.ClassLoaderTest 实例化的,但是在第二行输出中,发现这个对象与class org.fenixsoft.classloading .ClassLoaderTest 在进行类型检查时返回false。这是因为Java虚拟机中同时存在两个ClassLoaderTest类,一个是由虚拟机的应用程序类加载器加载的,另一个是由我们自定义的类加载器加载的,虽然它们都来自同一个Class文件,但它们在Java虚拟机中仍然是两个独立的类。检查对象类型的结果自然是false。
1.2 双亲委派模型
1.2.1 类加载器的分类:
粗略分类:
从Java虚拟机的角度来看,只有两种不同的类加载器:
一种是引导类加载器。这个类加载器是用C++语言实现的,是虚拟机本身的一部分;
另一个是所有其他类加载器。这些类加载器都是用Java语言实现的,独立存在于虚拟机之外,并且都继承自抽象类java.lang.ClassLoader。
细分:
从Java开发者的角度来看,类加载器应该划分得更细,即三层类加载器(JDK 8及之前的版本)。大多数Java程序都会使用系统提供的以下三个类加载器。加载)见补充说明1:
1.启动类加载器(Bootstrap Class Loader):
存放地点:
该类加载器负责加载存储在lib目录或-Xbootclasspath参数指定的路径中的、被Java虚拟机识别的类库并加载到虚拟机的内存中。
通过文件名来识别,如rt.jar、tools.jar,名称不一致的类库即使放在lib目录下也不会被加载。引用:
启动类加载器不能被Java程序直接引用。当用户编写自定义类加载器时,如果用户需要将加载请求委托给引导类加载器处理,那么直接使用null代替。参见补充说明2。2.扩展类加载器(Extension Class Loader):
实现方法:
该类加载器在类sun.misc.Launcher$ExtClassLoader中作为Java 代码实现。加载路径:
它负责加载libext目录下或者java.ext.dirs系统变量指定的路径下的所有类库。影响:
扩展:根据“Extension Class Loader”这个名字,可以推断出这是Java系统类库的扩展机制。 JDK开发团队允许用户将通用类库放置在ext目录下,以扩展Java SE功能。
JDK 9之后,这种扩展机制被模块化带来的天然扩展能力所取代。由于扩展类加载器是由Java代码实现的,开发者可以直接在程序中使用扩展类加载器来加载Class文件。 3.应用程序类加载器:
实现方法:
该类加载器由sun.misc.Launcher$AppClassLoader实现。
由于应用程序类加载器是ClassLoader类中getSystem-ClassLoader()方法的返回值,因此在某些情况下也称为“系统类加载器”。
影响:
它负责加载用户类路径(ClassPath)上的所有类库。开发者也可以直接在代码中使用这个类加载器。
如果应用程序没有定制自己的类加载器,那么这一般是程序中默认的类加载器。
补充说明1:
从JDK 1.2开始,Java一直保持着三层类加载器和父委托类加载架构。尽管这种架构在Java模块化(1.9)系统出现后经历了一些调整和变化,但其主要结构并没有改变。我们将在后面的章节中专门讨论模块化系统下的类加载器。
附加说明2:
/*
返回该类的类加载器。某些实现可能使用null 来表示引导类加载器。如果此类是由引导类加载器加载的,则此方法将在此类实现中返回null。
*/
公共类加载器getClassLoader() {
类加载器cl=getClassLoader0();
如果(cl==null)
返回空值;
SecurityManager sm=System.getSecurityManager();
如果(sm!=null){
ClassLoader ccl=ClassLoader.getCallerClassLoader();
if (ccl !=null ccl !=cl !cl.isAncestor(ccl)) {
sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
返回cl;
上面显示的是java.lang.ClassLoader.getClassLoader()方法的代码片段。注释和代码实现清楚地说明了使用空值来表示引导类加载器的约定。
1.2.2 双亲委派模型
image.png 上图所示的各种类加载器之间的层次关系被称为类加载器的“双亲委派模型”。
双亲委派模型的规则:
双亲委派模型要求除了顶层启动类加载器之外,所有其他类加载器都应该有自己的父类加载器。类加载器之间的父子关系的实现方式:
这里的类加载器之间的父子关系一般不是通过继承(Inheritance)关系实现的,而是通常使用组合(Composition)关系来复用父加载器的代码。
前面描述这种类加载器协作关系时,使用了双引号来强调这是一种“通常”的协作关系:
类加载器的双亲委托模型是在JDK 1.2 中引入的,此后几乎在所有Java 程序中都得到了广泛的应用。不过,它不是强制性的模型,而是Java 设计者向开发人员推荐的。类加载器实现的最佳实践。双亲委派模型的工作过程是:
如果一个类加载器收到类加载请求,它不会先尝试加载类本身,而是将请求委托给父类加载器来完成。对于每一级类加载器都是如此,因此所有的加载请求最终都应该传递到顶层启动类加载器。只有当父加载器报告无法完成加载请求(在其搜索范围内没有找到所需的类)时,子加载器才会尝试自己完成加载。双亲委派模型的好处:
使用双亲委派模型来组织类加载器之间的关系的一个明显好处是,Java 中的类与其类加载器具有优先级的层次关系。
例如,类java.lang.Object 存储在rt.jar 中。无论哪个类加载器想要加载这个类,最终都会委托给模型顶部的启动类加载器来加载。因此,Object类在程序中。在各种类加载器环境下都可以保证同一个类。
即启动类加载器的加载顺序高于其他加载器,这样可以保证JVM所需类的唯一性。相反,如果不使用双亲委派模型,每个类加载器自己加载的话,如果用户编写了一个名为java.lang.Object的类,并将其放入程序的ClassPath中,那么系统就会出现如果由多个不同的Object类组成,Java类型系统中最基本的行为就无法得到保证,应用程序就会变得混乱。
例子:
受保护的同步类?loadClass(字符串名称,布尔解析)抛出ClassNotFoundException
{
//首先检查请求的类是否已经加载
类c=findLoadedClass(名称);
如果(c==null){
尝试{
如果(父!=null){
c=parent.loadClass(name, false);
} 别的{
c=findBootstrapClassOrNull(名称);
}
} catch (ClassNotFoundException e) {
//如果父类加载器抛出ClassNotFoundException
//表示父类加载器无法完成加载请求
}
如果(c==null){
//当父类加载器无法加载时
//然后调用自己的findClass方法来加载类
c=findClass(名称);
}
}
如果(解决){
解析类(c);
}
返回c;
这段代码的逻辑清晰易懂:首先检查请求加载的类型是否已经加载。如果没有,则调用父加载器的loadClass()方法。如果父加载器为空,则默认使用启动类加载器作为父加载器。装载机。如果父类加载器加载失败并抛出ClassNotFoundException,那么它会调用自己的findClass()方法来尝试加载。
1.2.3 破坏双亲委派模型(选看)
如上所述,双亲委派模型并不是一种强制约束的模型,而是Java设计者向开发者推荐的一种类加载器实现方法。在Java 世界中,大多数类加载器都遵循这种模型,但也有例外。直到Java模块化的出现,双亲委派模型已经被大规模“打破”了3次。
打破这种模式的历史:
双亲委托模型的第一次“突破”其实发生在双亲委托模型——出现之前的“远古”时代,也就是JDK 1.2出现之前。
由于双亲委派模型是在JDK 1.2之后才引入的,所以类加载器和抽象类java.lang.ClassLoader的概念在Java的第一个版本中就已经存在了。面对现有的用户定义类加载器Code,Java设计者在引入双亲委托模型时不得不做出一些妥协。为了兼容这些已有的代码,不能再用技术手段来避免loadClass()被子类覆盖的可能性。只能在JDK 1.2之后的Java中完成。在.lang.ClassLoader中添加新的受保护方法findClass(),并引导用户在编写类加载逻辑时尽可能重写该方法,而不是在loadClass()中编写代码。上一节我们已经分析了loadClass()方法,这里实现了双亲委派的具体逻辑。根据loadClass()方法的逻辑,如果父类加载失败,会自动调用自己的findClass()方法来完成加载,所以并不妨碍用户按照自己的意愿加载类,但是还确保新编写的类加载器符合双亲委托规则。家长委托模型的第二次“崩溃”是由模型本身的缺陷造成的。
双亲委派很好地解决了各个类加载器协作时基本类型的一致性问题(越基本的类由高层加载器加载)。基本类型之所以被称为“基本”,是因为它们总是作为用户代码继承和调用的API而存在,但在编程中往往不存在绝对不变、完美的规则。如果有基本类型需要回调到用户代码中怎么办?
这并非不可能。一个典型的例子是JNDI服务。 JNDI 现在是Java 中的标准服务。它的代码由启动类加载器加载(在JDK 1.3中添加到rt.jar中)。它一定是Java中非常基本的类型。然而,JNDI 的目的是查找和集中管理资源。它需要调用其他厂商实现的、部署在应用程序的ClassPath下的JNDI服务提供者接口(SPI)的代码。
现在的问题是,通过启动类加载器是无法识别并加载这些代码的,那么我们该怎么办呢?
为了解决这个困境,Java设计团队不得不引入一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContext-ClassLoader()方法来设置。如果线程创建时没有设置,则会从父线程继承。如果应用程序的全局范围内没有设置的话,类加载器默认为应用程序类加载器。
有了线程上下文类加载器,程序就可以做一些“作弊”的事情了。 JNDI 服务使用该线程上下文类加载器来加载所需的SPI 服务代码。这是父类加载器请求子类加载器完成类加载的行为。这种行为实际上开启了家长委托模式。使用层次结构反向使用类加载器违反了双亲委托模型的一般原则,但我们对此无能为力。 Java中涉及SPI的加载基本上都是这样完成的,比如JNDI、JDBC、JCE、JAXB和JBI等。但是当SPI服务提供者多个时,代码只能根据类型进行硬编码特定提供商的。为了消除这种极其不优雅的实现,在JDK 6中,JDK提供了java.lang. util.ServiceLoader类基于META-INF/services中的配置信息和责任链模式,为SPI加载提供了一个相对合理的解决方案。
家长委托模式的第三次“崩溃”是用户追求节目活力造成的。
这里所说的“动态”指的是一些非常“热”的术语:代码热插拔(Hot Swap)、模块热部署(Hot Deployment)等。说白了,就是希望Java应用程序能够像我们的电脑外设一样。您可以连接鼠标或U盘并立即使用,无需重新启动机器。如果鼠标出现问题或者需要升级,只需更换鼠标即可,无需关机或重启。对于个人电脑来说,重启一次并不算什么大事,但对于某些生产系统来说,关机再重启一次就可能被归类为生产事故。在这种情况下,热部署对于软件开发人员来说非常重要,尤其是大型系统。或者对企业级软件开发者有很大的吸引力。 Java 社区中模块化规范的历史:
早在2008年,在Java社区的第一次模块化规范之争中,Sun/Oracle提出的JSR-294和JSR-277规范提案输给了IBM主导的JSR-291(即JSR-291)。 OSGi R4.2) 提案。尽管Sun/Oracle不甘心失去在Java模块化方面的霸主地位,立即推出了Jigsaw项目与之抗衡,但OSGi已经站稳了脚跟,成为业界“事实上的”Java模块化标准。长期以来,IBM 依靠OSGi 广泛的应用基础让Jigsaw 深受其害。这种影响一直持续到Jigsaw 随JDK 9 一起发布。而尽管Jigsaw 现在已经成为Java 的标准特性,但它仍然需要小心避免OSGi 运行时动态热部署的优点,并且仅限于静态解决封装隔离的问题以及模块之间的访问控制。现在我们就开始简单看一下OSGi是如何通过类加载器实现热部署的:
OSGi模块化热部署的关键是其自定义类加载器机制的实现。每个程序模块(在OSGi 中称为Bundle)都有自己的类加载器。当一个Bundle需要替换时,该Bundle与类加载器一起替换,实现代码的热替换。在OSGi环境中,类加载器不再具有双亲委托模型所推荐的树形结构,而是进一步发展为更加复杂的网络结构。当收到类加载请求时,OSGi将按以下顺序执行类搜索:
1)将java.*开头的类委托给父类加载器来加载。
2)否则,委托列表中的类将委托给父类加载器加载。
3) 否则,将Import列表中的类委托给Export类的Bundle的类加载器来加载。
4) 否则,找到当前Bundle的ClassPath并使用自己的类加载器加载它。
5) 否则,检查该类是否在其自己的Fragment Bundle 中。如果是,则委托给Fragment Bundle的类加载器来加载。
6) 否则,在Dynamic Import列表中找到该Bundle,委托给对应Bundle的类加载器进行加载。
7) 否则,类搜索失败。
上述搜索序列中只有前两点仍然符合双亲委托模型的原则。其余的类搜索在平面类加载器中执行。我不会详细介绍OSGi 的其他方面。
虽然本节使用“破坏”一词来描述上述不符合家长委托模型原则的行为,但这里的“破坏”并不一定具有贬义。只要有明确的目的和充分的理由,突破旧原则无疑是一种创新。正如OSGi中类加载器的设计并不符合传统的父委托类加载器架构,而且业界对其实现热部署带来的额外高复杂度还存在很多争议,但也有理解这方面的。技术人员基本可以达成共识,OSGi中类加载器的使用值得学习。如果你完全理解了OSGi的实现,那么你就掌握了类加载器的本质。
二.Java模块化系统
推出版本:
JDK 9中引入的Java平台模块系统(JPMS)是Java技术的重要升级。为了实现模块化——可配置封装和隔离机制的关键目标,Java虚拟机类加载架构也做了相应的改变和调整,使模块化系统能够顺利运行。 Java的模块定义还包含以下内容:
对其他模块的依赖关系列表。
可供其他模块使用的导出包的列表。
开放包列表,即可以被其他模块反射访问的模块列表。
使用的服务列表。
提供服务的实现列表。
模块化可配置封装隔离机制的优点:
避免了大部分由类型依赖引起的运行时异常
在JDK9之前,由于类是根据路径加载的,一旦类路径中缺少相应的依赖,只有在该类型的程序加载链接时才会报错。
JDK9之前,是根据类路径(ClassPath)来查找依赖的。如果类路径中缺少运行时依赖的类型,那么只有在程序运行时才会报告运行异常,直到发生该类型的加载和链接。 JDK9之后,如果启用模块化封装,模块可以声明对其他模块的显式依赖,让JVM在启动时验证依赖是否完整,如果缺失则直接报错。
JDK 9之后,如果启用模块化封装,模块可以声明对其他模块的显式依赖关系,这样Java虚拟机就可以在启动时验证应用程序开发阶段设置的依赖关系在运行时是否得到维护。完整,如果缺少什么,启动会直接失败,从而避免了类型依赖导致的大量运行时异常。对于公共类型,模块提供更细粒度的可访问性控制。需要明确声明哪些public类型可以被哪些模块访问,否则所有代码都无法访问public。
可配置的封装隔离机制还解决了原始类路径上的JAR文件之间公共类型的可访问性问题。
JDK 9中的公共类型不再意味着程序中所有部分的代码都可以随意访问它们。模块提供更精细的可访问性控制。有必要明确声明哪些公共类型可以被哪些其他模块访问。
访问,这种访问控制也主要是在类加载过程中完成的,具体内容笔者在前文对解析阶段的讲解中已经介绍过。2.1 模块兼容性
这里讲的兼容性,主要指以下特性: 兼容:传统的类路径查找机制 为了使可配置的封装隔离机制能够兼容传统的类路径查找机制,JDK 9提出了与“类路径”(ClassPath)相对应的“模块路径”(ModulePath)的概念。 简单来说,就是某个类库到底是模块还是传统的JAR包,只取决于它存放在哪种路径上。 只要是放在类路径上的JAR文件,无论其中是否包含模块化信息(是否包含了module-info.class文件),它都会被当作传统的JAR包来对待; 相应地,只要放在模块路径上的JAR文件,即使没有使用JMOD后缀,甚至说其中并不包含module-info.class文件,它也仍然会被当作一个模块来对待。 记忆版: 为了能够兼容传统的类路径查找机制,JDK 9提出了与“类路径”(ClassPath)相对应的“模块路径”(ModulePath)的概念,某个类库到底是模块还是传统的JAR包,只取决于它存放在哪种路径上。JDK9通过如下3条规则保证了即使Java应用依然使用传统的类路径,升级到JDK 9对应用来说几乎不会有任何感觉,项目也不需要专门为了升级JDK版本而去把传统JAR包升级成模块 JAR文件在类路径的访问规则:所有类路径下的JAR文件及其他资源文件,都被视为自动打包在一个匿名模块(Unnamed Module)里,这个匿名模块几乎是没有任何隔离的,它可以看到和使用类路径上所有的包、JDK系统模块中所有的导出包,以及模块路径上所有模块中导出的包。 模块在模块路径的访问规则:模块路径下的具名模块(Named Module)只能访问到它依赖定义中列明依赖的模块和包,匿名模块里所有的内容对具名模块来说都是不可见的,即具名模块看不见传统JAR包的内容。 JAR文件在模块路径的访问规则:如果把一个传统的、不包含模块定义的JAR文件放置到模块路径中,它就会变成一个自动模块(Automatic Module)。尽管不包含module-info.class,但自动模块将默认依赖于整个模块路径中的所有模块,因此可以访问到所有模块导出的包,自动模块也默认导出自己所有的包。 不兼容:Java模块化系统目前不支持在模块定义中加入版本号来管理和约束依赖,本身也不支持多版本号的概念和版本选择功能。2.2 模块化下的类加载器
为了保证兼容性,JDK 9并没有从根本上动摇从JDK 1.2以来运行了二十年之久的三层类加载器架构以及双亲委派模型。但是为了模块化系统的顺利施行,模块化下的类加载器仍然发生了一些应该被注意到变动,主要包括以下几个方面: 1.扩展类加载器(Extension Class Loader)被平台类加载器(Platform Class Loader)取代。 这其实是一个很顺理成章的变动,既然整个JDK都基于模块化进行构建(原来的rt.jar和tools.jar被拆分成数十个JMOD文件),其中的Java类库就已天然地满足了可扩展的需求 那自然无须再保留libext目录,此前使用这个目录或者java.ext.dirs系统变量来扩展JDK功能的机制已经没有继续存在的价值了,用来加载这部分类库的扩展类加载器也完成了它的历史使命。类似地,在新版的JDK中也取消了jre目录,因为随时可以组合构建出程序运行所需的JRE来 譬如假设我们只使用java.base模块中的类型,那么随时可以通过以下命令打包出一个“JRE” jlink -p $JAVA_HOME/jmods --add-modules java.base --output jre2.平台类加载器和应用程序类加载器都不再派生自java.net.URLClassLoader 如果有程序直接依赖了这种继承关系,或者依赖了URLClassLoader类的特定方法,那代码很可能会在JDK 9及更高版本的JDK中崩溃。 现在启动类加载器、平台类加载器、应用程序类加载器全都继承于jdk.internal.loader.BuiltinClassLoader,在BuiltinClassLoader中实现了新的模块化架构下类如何从模块中加载的逻辑,以及模块中资源可访问性的处理。 jdk9之前的类加载器继承架构image.pngJDK 9及以后的类加载器继承架构 image.png3.启动类加载器现在是在Java虚拟机内部和Java类库共同协作实现的类加载器,尽管有了BootClassLoader这样的Java类,但为了与之前的代码保持兼容,所有在获取启动类加载器的场景(譬如Object.class.getClassLoader())中仍然会返回null来代替,而不会得到BootClassLoader的实例。image.png4.JDK 9中虽然仍然维持着三层类加载器和双亲委派的架构,但类加载的委派关系也发生了变动。【《JVM虚拟机核心原理深度解析》读书心得分享】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
看了题目就感觉很酷!一直想了解下jvm机制,这篇文章好像很有用啊。
有12位网友表示赞同!
现在 Java 基础都掌握了,下一步就是看这篇文章深入学习 JVM 了!
有6位网友表示赞同!
深入理解?听起来很专业的。我要找来这本书好好看看!
有13位网友表示赞同!
学习 JVM 太重要了,这东西关系到性能调优啊。
有11位网友表示赞同!
做 Java 开发久了,对JVM的运作机制还是有些模糊的概念,这种读书笔记应该很有帮助。
有17位网友表示赞同!
网上那么多关于JVM的文章,感觉这部读书笔记会更加系统和全面吧?
有5位网友表示赞同!
能详细了解jvm虚拟机是挺有价值的!这篇文章会不会讲到垃圾回收算法啊?
有18位网友表示赞同!
如果能分享一下笔记中的重点或者总结,就太棒了。
有20位网友表示赞同!
想了解更多 JVM 相关的优化技巧,这篇读书笔记或许可以提供一些思路。
有15位网友表示赞同!
学习 JVM 不容易,需要花费不少时间和精力 。
有12位网友表示赞同!
学习机器学习的时候也会用到 JVM,这篇文章对我很有用。
有20位网友表示赞同!
希望这本书能讲明白些枯燥的理论,让我更容易理解JVM。
有12位网友表示赞同!
真希望能找到一篇能够真正深入浅出讲解 JVM 的笔记!
有17位网友表示赞同!
分享一下你的读书笔记可以帮到很多需要学习 JVM 的人啊!
有5位网友表示赞同!
有些技术细节感觉很扎心,希望这本书能解释清楚
有19位网友表示赞同!
深入理解JVM的笔记应该是非常难得的宝藏!
有16位网友表示赞同!
我一直在寻找一些高质量的 JVM 学习资料,这篇文章看起来不错!
有18位网友表示赞同!
学习 JVM 是一个长期的过程,需要不断积累和实践。
有13位网友表示赞同!
希望能了解更多关于 JVM 架构和内存管理的文章。
有10位网友表示赞同!