当前位置: 首页 > 网络应用技术

在-Depth探索Java应用程序的启动速度优化

时间:2023-03-06 12:07:56 网络应用技术

  简介:在高性能的背后,Java的宽松表现也令人印象深刻。Java对每个人的笨重和缓慢印象的大部分印象也来自这。似乎与高性能和快速启动速度背道而驰。本文将探讨两者是否可以与您同在。

  Java作为一种面向对象的编程语言,在性能方面具有独特的性能。

  “跨编程语言的能源效能,能源,时间和记忆如何联系?”它调查了主要编程语言的执行效率。

  从表格上,我们可以看到Java的执行效率很高,约占C语言最快的一半。这仅次于C,Rust和C ++在主流编程语言中。

  Java在Hotspot.java的服务器编译器(C2)编译器中的出色JIT编译器的出色性能受益是Cliff Click博士的作品,并使用了节点模型。这项技术还证明了它代表了最先进的水平行业:

  在高性能的背后,Java的启动性能差也令人印象深刻。印象中,Java的大多数不舒服和缓慢的印象也来自此。似乎与高性能和快速启动速度背道而驰。本文将探讨两者是否可以与您同在。

  雅加达(Jakartaee)是Oracle向Eclipse Foundation捐赠J2EE后的新名称。Java于1999年推出J2EE规格。EJB(Java Enterprise Beans)定义了IOC,AOP,AOP,Transaction,Confaction,Collasterent和Enterprise Development所需的功能设计非常复杂。最基本的应用程序需要大量的配置文件,这非常不便。

  随着互联网的兴起,EJB逐渐被更轻巧,更免费的弹簧框架所取代,春季已成为Java Enterprises开发的事实标准。尽管春季更轻,但它仍然在很大程度上受到Jakartaee的影响。。例如,在早期版本中使用大量XML配置,大量jakartaee(例如JSR 330依赖项注入)和规范(例如JSR 340服务API)的使用。

  但是春天仍然是企业级别的框架。我们看一下几个春季框架的设计理念:

  在这种设计理念的影响下,必须有大量可配置和初始化逻辑,以及支持这种灵活性的复杂设计模型。我们通过测试来查看它:

  我们运行了一个弹簧启动 - 网络。通过-verbose:类,您可以看到因类别文件:

  班级的数量达到了惊人的7404。

  让我们比较JavaScript生态学,并使用常用的表达来编写基本应用程序:

  我们使用Node的调试环境变量分析:

  它仅取决于55个JS文件。

  尽管Spring-Boot和Express不公平。在Java世界中,它还可以基于更轻巧的框架(例如Vert.x和Netty)构建应用程序,但实际上,每个人几乎都会选择Spring-boot,以享受便利性Java开源生态学。

  Java是否因为框架很复杂而开始缓慢吗?只能说答案是复杂的框架是慢慢启动的原因之一。通过GraAlvm的本机图像函数弹簧本特征的组合的本机图像函数,弹簧启动应用程序可以缩短约十次。

  Java的口号是“写一次,在任何地方运行”(WORA),Java确实通过字节代码和虚拟机技术实现了这一点。

  WORA使开发人员能够开发和调试MACOS,可以快速部署到Linux服务器。Cross -Platform还使Maven Center仓库更容易维护,这有助于Java开源生态系统的繁荣。

  让我们看一下Wora对Java的影响:

  Java通过课程组织源代码,然后将类塞入JAR软件包中以进行模块和分发。JAR软件包本质上是一个ZIP文件:

  每个JAR软件包都是一个相对独立的模块,开发人员可以按需依靠特定功能。JVM通过类路径知道这些罐子并加载。

  基于它,将其执行到新的或InvoKestatic Bytecode.jvm时将加载它,将为ClassLoader提供控制。URLCLASSLOADER的最常见实现将穿越JAR软件包,以找到相应的类文件:

  因此,搜索类的费用通常与JAR软件包的数量成正比。在大量的大规模场景中,将有数千个,这将导致整体搜索需要高度时间。

  找到类文件后,JVM需要检查类文件是否合法,并将其分析为内部数据结构。在JVM中,它称为Instanceklass。我已经听到了类文件中包含的信息:

  该结构包含接口,基类,静态数据,对象的布局,方法字节,常数池等。这些数据结构对于解释器来说是必须执行字节代码或JIT编译所必需的。

  加载类后,必须完成初始化以创建对象或调用静态方法。类初始化可以简单地理解为静态块:

  上面的第一个静态变量Java_version_string的初始化也将成为静态块的一部分。

  类初始化具有以下特征:

  这些特征非常适合读取配置或构建数据结构,缓存等。在运行时,因此,许多类别的初始化逻辑将更加复杂。

  Java类可以在初始化后实例对象并在对象上调用方法。解释执行大开关。案例循环,性能很差:

  我们使用JMH运行Hessian序列化微观基准测试:

  第二个-XINT参数控制了我们仅使用解释器,这是26倍。这是由直接机器执行的执行和解释之间的差异所带来的。这一差距与场景有着巨大的关系,我们通常的经验是50次。

  让我们看一下JIT的行为:

  这是两个JDK中JIT参数的值。我们暂时没有向分层的汇编原理介绍太多。您可以参考堆栈溢出。Tier3可以简要理解为(客户端编译器)C1,Tier4为C2。在本文开头,这确实实现了C的一半。

  在启动阶段,该方法尚未由JIT完全编译,因此大多数情况都保留在解释执行中,这会影响应用程序启动的速度。

  早些时候,我们花了很多空间来分析开始Java应用程序慢速启动的主要原因。摘要是:

  这两个全面导致了Java应用程序的当前状态缓慢。

  Python和JavaScript都动态解析了加载模块。Cpyhton甚至没有JIT。从理论上讲,启动不会比Java快得多,但是他们不会使用非常复杂的应用程序框架,因此不会感觉到启动性能的总体问题。

  尽管我们不能轻易更改用户的框架习惯,但我们可以在运行时进行增强,因此启动性能尽可能接近本地图像。OpenJDK的官方社区也一直在努力解决启动工作性能问题。因此,作为普通的Java开发人员,我们可以使用OpenJDK的最新功能来帮助我们提高启动性能吗?

  面对OpenJDK上述功能中存在的问题,阿里巴巴·龙韦尔(Alibaba Dragonwell)已得到优化和优化上述技术,并与云产品集成。用户可以轻松地优化启动时间,而无需投入过多的精力。

  1. AppCD

  首次在Oracle JDK1.5中引入了CD(类数据共享),在Oracle JDK8U40中引入了APPCD来支持JDK,但作为业务特征提供了。随后,Oracle为社区贡献了APPC,并在JDK10中逐渐改进了CDS,逐渐改进了CDS。,它还支持用户定义的类加载程序(也称为AppCDS V2)。

  面向对象的语言绑定对象(数据)和方法(对象上的操作)以提供更强的包装和多态性。这些特征依赖于标题头中的类型信息来实现,而Java和Python语言是这种情况。内存中的Java对象如下:

  Mark代表对象的状态,包括该对象是否已锁定,GC年龄等等。

  基于此结构,诸如o实例字符串之类的表达式可以有足够的信息来判断。应该注意的是,实例klass结构相对复杂,包括类,字段等的所有方法,该方法还包含此类信息作为字节代码。该数据结构是通过在运行时解析类文件来获得的。为了确保安全性,分析类时需要字节代码的合法性(由JAVAC方法生成的字节码很容易导致JVM崩溃)。

  CD可以存储通过此分析和验证生成的数据结构存储(DUMP),并在下一个运行时重复使用它。此转储产品称为共享存档,带有JSA苦难(Java共享存档)。

  为了减少CD读取JSA转储的开销,并避免将数据的数据序列化为Instanceklass,JSA文件中的存储和Instanceklass对象完全相同。这样,当使用JSA数据时,您只需要将JSA文件映射到内存,然后将对象头部的类型指针指向此内存地址,这非常有效。

  1. Instanceklass不是从客户类加载程序中存储的,是Class Class Loader的class file.for boot classloader(即JRE/lib/rt.jar)和System(App)ClassLoader(下面的类下方的ClassLoader),CD可以跳过类文件的读数。仅通过类名与相应的数据结构匹配。

  Java还为用户定义的类加载程序(自定义类加载程序)提供了一种机制。用户可以通过Override自己的ClassLoader.loadClass()方法高度自定义类的逻辑。例如,在提高APPCD的安全性并避免非指望类的情况下,因为类定义是从CDS加载的,AppCDS客户类加载程序需要通过以下步骤:

  我们看到了许多场景,上面的第一步占据了加载的时间。目前,AppCD似乎是无能为力的。例如:

  类路径包含如上图所示的三个JAR数据包。加载com.foo.foo类时,大多数ClassLoader实现(包括UrlClassloader,Tomcat,Spring-Boot)选择了最简单的策略(过早优化是所有邪恶的来源。):按照JAR的顺序,请尝试绘制绘制com/foo/foo.class的文件按磁盘的顺序。

  JAR软件包使用ZIP格式作为存储。每个班级的加载都需要在类路径下穿越罐子袋。尝试从ZIP绘制一个文件,以确保可以找到现有的类。修理N Jar袋,然后需要尝试访问N/2个ZIP文件的平均类加载。

  在我们的真实场景之一中,N到2000年。目前,罐装套餐搜索费用非常大,远大于Instanceklass的分析费用。。

  2.罐子索引

  根据JAR文件规范,JAR文件是一种使用ZIP包装的格式,并使用文本将元信息存储在Meta -Inf目录中。此格式在设计过程中考虑了上述搜索场景。该技术称为JAR索引。

  假设我们想在上面的bar.jar,baz.jar,foo.jar中找到一堂课。如果您可以使用com.foo.foo类型,可以立即推断特定的JAR软件包,可以避免上述扫描费用。

  通过JAR索引技术,可以生成上述索引文件索引。list.list.list。加载到内存后,成为hashmap:

  当我们看到类名称com.foo.foo时,我们可以根据软件包名称com.foo学习特定的jar package foo.jar。

  JAR索引技术似乎可以解决我们的问题,但是该技术很旧,很难在现代应用中使用:

  Dragonwell通过代理的注射使索引可以正确生成,并在Class Path的适当位置出现,以帮助应用程序改善启动性能。

  2.预先初始化课程

  类的静态块中的代码执行我们称为类初始化的内容。加载类后,必须执行初始化代码(创建实例并调用静态方法)。

  许多类别的初始化本质上只是构建一些静态文件:

  我们知道,JDK在框类型中使用的部分中有一个缓存,以避免重复过多的创建。这些数据需要提前构建。由于这些方法仅执行一次,因此以纯粹的解释方式执行。如果您可以说服几个静态字段以避免调用类初始化,我们可以获得预先初始初始化以减少启动时间的类。

  将持久性加载到内存的最有效方法是内存映射:

  C语言几乎直接面向内存以操作数据,而高级别的语言(例如Java抽象存储器)进入对象。有元信息,例如马克,klass*智慧,以获得有效的目标持久性。

  1.堆存档介绍

  OpenJDK9引入了剧烈的功能,正式使用了OpenJDK12中的Heap Archive。顾名思义,Heap Archive Technology可以持续在堆上的对象上。

  对象图提前构建,并将其放入存档。我们将这个阶段称为垃圾场;并且存档中的数据在运行时被调用。调整和运行时通常不是一个过程,但是在某些情况下,同一过程也可以相同。

  在使用APPCD之后,请回想内存布局,对象的Klass*指针指向sharedarchive.appcds中的数据。如果您想重复使用持续的对象,则对象头的类型指针还必须指向一块可持续的元信息,因此,较炙手可热的技术依赖于AppCD。

  为了适应各种情况,OpenJDK HeaterParchive还提供了两个级别:开放和关闭:

  上图是允许的参考关系:

  该设计的原因是,对于某些仅阅读结构,放置在封闭的档案中,您可以没有GC的费用。

  为什么只读它?想象一下,如果封闭存档中的对象a在堆中引用对象b,那么当对象B移动时,GC需要将字段点调整为A中的B,这将带来GC开销。

  2.使用堆存档

  在支持此结构之后,加载了类后,将静态变量定向到存档的对象,以完成类初始化:

  3. AOT汇编

  除了课程的加载外,该方法的前几个执行是在解释模式下执行的,因为它没有由JIT编译器编译。- 在JIT汇编后的第十个。代码解释的执行速度较慢,也是一个重大的罪魁祸首,开始缓慢。

  传统语言(例如C/C ++)直接编译为直接编译为目标平台的本机机器代码。每个人都意识到口译员的开始问题,例如Java和JS,字节代码直接编译为本机通过AOT代码逐渐进入公众愿景。

  WASM,GRAALVM和OPERJDK都在不同程度上支持AOT汇编。我们主要优化JEP295引入的JAOTC工具周围的启动速度。

  在此处注意:

  JEP295使用AOT将类文件中的方法汇总到本机代码片段中,一一将替换方法的替换方法的入口加载到AOT代码之后,加载了AOT代码。GRAALVM的天然图像函数是更彻底的静态汇编。通过Java代码编写的小运行时,substrateVM由运行期间的静态文件(类似于GO)编译,并且不再依赖JVMessencEthis方法也是AOT,但为了区分术语,AOT单个是指JEP295的方法。

  1. AOT功能的首次体验

  通过引入JEP295,我们可以快速体验AOT

  JAOTC命令调用GRAAL编译器来编译字节代码以生成Libhelloworld.so文件。此处生成的SO文件很容易使人们错误地认为它会直接调用JNI之类的库库代码。LD加载机制运行代码。因此,文件更像是一个容器作为本机代码。HOTSOPTRUNTIME需要在加载AOT后进行进一步的动态链接。加载类后,加载了类,热点将自动关联AOT代码入口,并为下一个AOT版本使用AOT版本方法。AOT生成的代码还将与热点积极交互,在AOT,解释器和JIT代码之间跳跃。

  1)一波AOT

  JEP295似乎已经达到了一个完整的AOT系统,但是为什么您会看到该技术大规模使用?在OpenJDK的新功能中,AOT可以被视为很多生活。

  2)多级加载程序问题

  JDK-8206963:带有多个类加载程序的错误

  这不是在设计中考虑Java的多级载体场景。当多个classloader使用同名使用AOT时,共享其静态字段。

  因为没有解决此问题可以快速修复此问题的解决方案,因此仅添加了OpenJDK以下代码:

  对于用户自定义类加载程序,不允许AOT。从这里,可以看出,此功能在社区层面上逐渐缺乏维护。

  在这种情况下,尽管仍然可以通过类Path指定的类使用AOT,但我们通常需要使用的框架,例如Spring-Boot,Tomcat,可以通过自定义class Loader加载应用程序代码。可以说,此更改切断一大块AOT。

  3)缺乏调整和维护,返回实验特征

  JDK-8227439:默认关闭AOT

  和严格的足迹含义。

  从那时起,您需要添加实验参数:

  根据问题描述,此特征编译了整个模块,该模块对起始速度和记忆职业有反应。我们分析的原因如下:

  4)在JDK16中删除

  JDK-8255616:在Oracle OpenJDK中禁用AOT和GRAAL

  在OpenJDK16发行的前夕,Oracle正式决定不维护这项技术:

  我们没有看到对这些功能的太多使用,支持和增强它们所需的效果很大。

  基本原因是这是基于缺乏必要的优化和维护。对于与AOT相关的未来计划,只能从未来Java AOT有两个技术方向的词中推测:

  上面的两个技术方向在短期内看不到进展。因此,Dragonwell的技术方向是使现有的JEP295更好地工作,并将最终的启动性能带给用户。

  5)迅速从Dragonwell开始

  Dragonwell的快速启动功能具有APPCD和AOT编译技术的弱点,并基于HeaterParchive机制开发了预先的初始化功能。这些功能几乎完全消除了JVM可见应用的启动。

  此外,由于上述技术与Trace-Dump重新播放使用模式一致,因此Dragonwell将上述加速技术统一并将其集成到SAE产品中。

  借助良好的成分,它还需要与调味品和烹饪大师相匹配。

  Dragonwell的开始加速技术和以其弹性而闻名的无服务器技术的结合相互补充。同时,它可以在微服务的整个生命周期管理中共同实施,以缩短应用程序结束的作用 - 到末端启动时间。因此,Dragonwell选择选择Choosesae来推出其发射加速技术。

  SAE(无服务器应用程序引擎)是第一个用于无服务器的PAAS平台。他可以::

  Java软件包部署:零代码转换以享受微服务,降低研发成本无服务器的极限弹性:资源无服务的运营和维护,快速扩展申请示例,减少操作,维护和学习成本

  1.难度分析

  通过分析,我们发现微服务用户在应用程序启动级别面临一些问题:

  在Dragonwell的快速启动功能的帮助下,SAE提供了一组无服务的Java应用程序,以允许应用程序尽可能多地加速最佳实践启动,以便开发人员更多地专注于业务开发:

  2.加速效应

  我们选择一些微服务和复杂的依赖业务场所典型的演示或内部应用。测试启动效应,发现应用程序通常将启动时间减少5%至45%。如果申请开始,则会有显着的加速效应:

  3.客户案例

  ALI内部的无服务器平台的搜索和建议将隔离机制加载到同类中,并在同一Java虚拟机中部署了多个业务的合并。调度系统将按需将业务代码合并到免费的容器中,以便多家企业可以共享相同的资源池,从而大大提高了部署密度和总体CPU使用率。

  因为有必要支持大量不同的业务,所以平台本身需要提供足够的功能,例如缓存和RPC调用。Pandora Boot,它将加载大量类并拖动平台自己的启动速度。当需求增加时,调度系统需要提取更多的容器进行业务代码部署。目前,容器本身的启动时间尤为重要。

  基于Dragonwell的快速启动技术,搜索和推荐平台将在Pre -Release环境中对AppCD,Jarindex等进行优化,并将生成的存档文件键入容器映像。这样,每个容器在启动时就可以享受加速,并将约30%的时间降低约30%。开始时间。

  由SAE和Dragonwell 11提供的JAR软件包部署的外部客户,迭代迭代并迭代潮汐品牌购物中心应用程序。

  面对大规模尖峰,借助SAE无服务器的极端弹性,以及QPS RT索引的弹性,其使用指标很容易面临超过10倍以上的需求;开始 - UP时间正在消耗,进一步加速了应用程序的开始,以确保企业的平稳和健康运营。

  Dragonwell上的快速启动技术方向完全基于OpenJDK社区。它已经仔细优化和错误,并减少了入门的困难。这不仅确保了标准的兼容性,还可以避免内部定制,而且还为开源社区做出了贡献。

  作为一个基本软件,Dragonwell只能在磁盘上生成/使用存档文件,并用SAE无缝集成Dragonwell,JVM配置和存档文件的分布是自动化的。Customers可以轻松地享受应用程序加速所带来的技术股息。

  关于作者:

  来自阿里巴巴云Java虚拟机团队的Liang XI负责Java Runtime的方向。领导Java Corporation的研究和开发,开始优化和其他技术以及大型降落。

  阿里巴巴云SAE团队的序言负责运行时的演变,弹性和效率。

  原始链接:http://click.aliyun.com/m/1000293808/

  作者:Liang XI

  原始资料来源:阿里巴巴Yunyun Qi编号