下一代的JDK - GraalVM

GraalVM是最近几年Java相关的新技术领域不多的亮点之一, 被称之为革命性的下一代JDK,那么它究竟有什么神奇之处,又为当前的Java开发带来了一些什么样的改变呢,让我们来详细了解下

下一代的JDK

官网对GraalVM的介绍是 “GraalVM 是一个高性能 JDK,可提高基于 Java 和 JVM 的应用的性能并简化 Java 云原生服务的构建和运行。它提供优化的编译器,可以更快地生成代码并降低计算资源消耗,实现微服务即时启动“

这里面有几个关键词:高性能云原生低资源即时启动。而这几个关键词其实恰恰也是当前Java应用开发面临的困境。大家都知道当前不管是企业服务还是互联网领域都在强调云原生开发,但云原生的一个核心要素就是需要资源和服务的快速调度,毕竟服务器和带宽都是要钱的,用户只想在使用服务时付费,而不是长时间让应用空转。比如目前流行的serverless模式,要求函数的生命周期尽可能的短,意味着频繁的启动和销毁,这对于服务的启动性能,资源消耗,服务包大小都有比较高的要求。但对于当前的JAVA应用来说却是无可承受的痛。所以为啥现在go,rust这些语言大行其道,其实就是迎合了云原生的需求场景。Java作为一个拥有20多年历史的语言,当然不可能坐以待毙,所以也在不断的探索怎么提升启动时间,减少资源消耗,GraalVM应该算是目前这些探索当中最靠谱,最成熟的一个。

如果GraalVM真的能解决以上的问题,那称其为革命性的下一代JDK或者说是云原生时代Java的希望之星也不为过吧。目前包括Spring Boot, Micronaut, Quarkus 这些知名框架也都对GraalVM提供了原生的支持。

那GraalVM究竟是如何做到这一切的呢?我们先来看一下GraalVM的架构图
在这里插入图片描述

从图上可以看出来,其实GraalVM核心是由两个部分构成的。其中之一是一个全新的名为Graal的编译器,它是完全用Java语言编写的,就是图中红色的部分,这也是GraalVM的名字的来源,可想而知这个编译器的重要性。

另外一个重要的组成部分就是用于构建高性能动态语言解释器的框架叫做 Truffle,就是最上面的这一层,它也是GraalVM能够支持多种编程语言运行在其上的基础框架,它现在支持的语言有很多呀,包括pyhton,js这些常见的脚本语言,借助于LLVM编译器,甚至可以让C或者C++都跑在GraalVM之上,这在以前是很难想象的。

最底层就是我们非常熟悉的 Hotspot JVM,这部分GraalVM 并没有对Hotspot JVM做大的调整,包括垃圾回收器,类加载器,线程管理,诊断和监控等特性都是直接复用Hotspot JVM的功能,这也保证了GraalVM能够与现有的JAVA生态保持完全的兼容,只是专注于编译优化和性能提升

Graal编译器

Graal实际上是一个用Java语言编写的JIT编译器。它的主要作用就是替换原本Hotspot JVM中用C++写的另外两个JIT编译器:C1和C2。C1主要是用client模式下,我们应该接触的比较少,C2是Hotspot JVM自带的高性能JIT编译器,当年Hotspot JVM出现时,正是凭借基于热点探测的JIT技术在性能上吊打其它的一众JVM,成为JAVA领域这么多年来事实的标准,它的名字(HOTSPOT)也正好反应了它的这个重要特性。

为啥还需要另一个JIT编译器

既然有了C2这样强大的JIT编译器,为啥还要重新开发一个呢?而且还是用Java语言来开发,Java的性能能比C++高吗?其实重写这个编译器还真不是为了优化性能,C2的性能已经非常好了,问题的关键在于C2是C++写的,而且还写的很复杂,年代也很久远,这让C2项目变得非常难以维护和扩展。

我们来看看来自C2编译器背后大佬Cliff Click的抱怨:

在这里插入图片描述
在这里插入图片描述

于是用JAVA这样一种更容易维护,安全性也要比C++高很多的语言来重写编译器也就不是 一件不可想象的事情了。至于性能,Graal毕竟比C2晚了快20年,后发优势是非常明显的,可以做到一些原来在C2中很难做到的编译优化,比如部分逃逸分析,经过测试目前Graal编译器的峰值性能已经追平甚至部分超越了C2。

另外,为了让类似Graal这样的编译器也能够有效的和底层的Hotspot VM配合工作,JAVA社区还提出了一个名为JVMCI(JAVA虚拟机编译器接口) 的提案,本质上就是将JIT编译器和底层的虚拟机解耦,提供更强的扩展性。

Java的自举

用Java编写Java的编译器,这也带来了一个里程碑式的变化,Java终于能够自举了!我们来撸一撸其中的过程:

在这里插入图片描述

首先,我们用java写的源代码会被javac工具编译成平台无关的字节码,这个javac工具就是完全用java语言实现的。然后字节码在运行时通过jvm的 解释器或者JIT 编译器编译成本地代码,这个解释器和JIT编译器也是完全用java语言编写的,这样就完成了闭环,这使得JAVA能够完全摆脱对其它语言的依赖,从而成为一门完全自主的语言。

静态编译

前面的Graal编译器虽然意义重大,但依然只是一个JIT编译器,Java应用的编译执行方式和原来也没有多大的区别,如果仅仅是这样那显然GraalVM也不会有这么大的影响力。我们再来看一个真正可称之为GraalVM的杀手锏的功能—静态编译

JIT与AOT

我们都知道,Java为了实现其平台无关性,首次编译(javac)的产出物其实是平台无关的字节码,这些字节码是无法直接被执行的。最早的Java Runtime都是通过JVM内置的解释器直接将字节码即时转换成本地代码来执行的,这样解释执行的方式肯定慢呀,所以当HOTSPOT JVM首次引入了JIT—即时编译或者叫做动态编译这样的概念以后,JAVA才真正确定了自己在业界的地位,也让HOTSPOT JVM一直作为官方虚拟机延续到了现在。

JIT(Just-in-Time) 简单的说,就是在运行时,把一部分热点代码,就是反复会调用的方法直接编译成本地代码,从而提升程序的运行效率。大家可能会有这样的疑问,为啥只编译热点代码呢,全部都编译成本地代码不是更快吗?因为编译很慢呀,开销也很大,如果编译消耗的资源都大于整个程序消耗的资源了,那么这么做就是本末倒置了。所以JIT就是一种折中的做法,把频繁运行的方法编译成本地代码提升执行效率,其它偶尔才运行的代码还是通过解释器去执行,这样能够最大程度的平衡启动时间,资源消耗和性能。

– 与之相对的就是静态编译,也叫做AOT(Ahead-of-Time) 编译。这个名词最早被大家听说应该是在android领域,在安卓5.0版本,当时谷歌引入了ART的新的运行时环境,并推出了AOT编译模式,故名思意AOT编译是在应用安装时一次性把字节码编译成机器码,而不是像JIT那样切香肠式的编译,这样能大幅提升应用的启动速度。但早期的AOT编译会造成应用安装时间过长,空间占用较多的问题,后来谷歌又改成AOT和JIT混合编译的策略了。说回JAVA哈,安卓和JAVA本就是同源的,安卓可以AOT编译,JAVA当然也可以,最早在JAVA 9.0 , 就引入了这方面的概念和支持,这个版本有一个AOT编译器叫做jaotc(对应原来的javac),但因为各种原因一直没得到多少重视。

我们可以通过一张图来比较一下两种编译方式各自的特点:

在这里插入图片描述

一般来说,AOT在启动速度,内存占用,包体积等方面占优势,但编译过程较慢;而JIT编译很快,二期因为可以基于运行时的信息做更激进的编译优化,因此在峰值吞吐量和响应延迟这些方面可能会更有优势。

Native Image

AOT和JIT应该说是各有所长,不存在谁取代谁的问题。但在云原生,微服务架构大行其道的当下,大家更看重的还是应用的启动速度和资源占用等特点,所以静态编译又变成了一个非常有诱惑力的选型。GraalVM也提供了自己的java静态编译工具,也就是 Native Image 。这个工具可以直接将Java应用程序编译成可本地执行的文件,脱离虚拟机直接运行。它背后需要解决三方面的问题:

编什么

源代码肯定要编译,但是依赖呢?是不是所有依赖都要编译。理论上所有依赖都编译肯定是没有问题的,而且后面反射的问题也顺带就解决了。但现实场景这基本不可能,我们现在的应用程序依赖链一般都很深,如果把classpath上能扫描到的jar包都给编译了,那估计真的要等到天荒地老了。所以这一步是走不通的。

Native Image采用的方法是构建一个从应用程序的入口函数,也就是main方法开始,到所有可达代码的这样一个调用图。这个调用图可以认为是应用程序在实际执行时所有可能的执行路径之和,只要构造了这样的图,也就知道了哪些方法最终是可能被执行的,这些方法也就需要被编译。

在这里插入图片描述

如何适配JVM运行时

JVM运行时可不单是提供JIT的支持,垃圾回收,反射,动态代理这些所谓的运行时特性也需要在编译成静态本地代码时解决。这个的解决方案有点简单粗暴,就是直接在编译后的执行文件中塞一个精简后的JVM— SVM(Substrate VM) ,它包括了一组轻量级的运行时库和基础设施,提供包括内存管理、垃圾回收、线程管理等一系列基础特性。

除了这些,还要解决反射调用,JNI调用这类动态调用的问题。刚才也提到过,Native Image编译的时候会做静态分析,从主函数入口开始遍历所有的可达的方法作为整个编译范围,但是这样的扫描有一个缺陷,就是无法确定那些只有在运行时才会获取到的动态调用信息,也就不会将动态调用的目标加入到编译范围,这样在运行时肯定就报错了。而且现在诸如spring这样的框架,大量使用了反射调用,如果置之不理,那么常规的应用直接编译肯定都无法正常使用的。

Native Image的解决方式是,开发者主动以配置文件的方式告诉编译器哪些方法是动态调用,这样编译器就能提前将其加入到编译范围中,避免运行时出现问题。当然这个配置文件手搓肯定不现实,就算自己写的代码可以统计,那么第三方库里面这么多动态调用也是无法统计的。所以还有一个配套的小工具:native-image-agent,应用程序可以先以jar包的方式进行预执行,并配置这个代理。这个代理程序会收集应用程序在执行期间所触发的所有动态调用信息,并自动生成配置文件。

$JAVA_HOME/bin/java -agentlib:native-image-agent=config-output-dir=/path/to/config-dir/,config-write-period-secs=300,config-write-initial-delay-secs=5 ...

执行之后就会生成类似下面这样的配置文件:

在这里插入图片描述

有了这个配置文件以后,Native Image就会在编译时将反射的目标对象也加入到编译范围当中,并同时填充ReflectionData结构中,这个ReflectionData可以简单理解为反射对象的数据缓存,如果是在JVM中执行反射,第一次会先执行一次JNI调用,通过本地方法获取反射目标,再加入到ReflectionData缓存中,后续调用直接从缓存中读取目标对象信息,所以首次调用的开销要远高于后续的调用。而SVM在静态编译时就可以根据配置信息填充这个ReflectionData,这样反射调用就会比在JVM中要高效的多,这也是静态编译的一个优势。最后就是执行阶段,应用只需要简单的查询ReflectionData是否有相关目标信息,没有就直接报错,有才会调用。 整个反射的实现原理大概就是这样:

在这里插入图片描述
在这里插入图片描述

怎么编

最后再来看一下如何进行编译。编译器其实还是之前提到过的那个Graal编译器,但静态编译和JIT编译相比还是存在一些局限性,影响最大的就是无法获取运行时信息进行优化,比如如果基于运行时数据,JIT编译器发现某个IF判断几乎只执行其中一个分支,而另一个分支很少执行,那么JIT编译器在生成机器指令的时候就可以在IF判断前就准备好其中高频分支的相关数据,让后续代码能够快速执行,这就是所谓的分支预测。但静态编译因为不存在运行时数据,所以也就做不了相应的优化了,这里我列了一些静态编译和动态编译在编译期优化时的区别:

在这里插入图片描述

总结

GraalVM提供了Java应用程序运行的另一种方式,这将带来深远的影响,在云原生时代JAVA语言也终于有了自己的一席之地了。它主要有以下几个方面的好处:

  • 即时启动: 这个效果非常明显,基本就是所谓的秒起,和原来动辄等待十几二十秒简直是天壤之别。

  • 节约内存 : 经过实测空载内存差不多能够减少到原来的五分之一左右,虽然仍然比同一规模的go,rust等应用占用内存要多,但已经很惊艳了。

  • 安全性 : 这个可能是很多同学没想到的,这要从两个方面来分析:首先是静态编译仅编译实际应用需要的类和方法,这也意味着你即使引用了一个可能有安全漏洞的包,只要你实际运行的代码里面没有调用它有漏洞的方法,那就不会被攻击,相当于减少了被攻击的几率。另外Java之前反编译的风险是非常大的,因为编译产物是与源码结构类似的字节码,除非用一些比较复杂的方式来做编译期混淆,例如我们现在用的三方混淆插件,有多难用大家应该深有体会了。而静态编译后生成的是本地代码,不说完全解决了反编译的风险,至少安全性也是大大的提升了

但是静态编译如果要大规模应用到生产环境,目前还是存在一些局限性的,主要有下面一些:

  • 编译繁琐 GraalVM支持反射需要依赖于独立的配置,目前虽然可以通过native-image-agent的方式来自动生成配置文件,但这个依赖于预执行的路径覆盖,如果不能在预执行阶段做到覆盖主要的执行分支,那么有可能生成出来的反射配置文件也是不完整的。这个建议如果要在产品中使用的话,一定要为代码编写对应的单元测试用例,并尽可能覆盖到主要的业务分支,便于agent收集运行时数据进行编译。

  • 调试工具的缺乏 编译成可执行文件之后,原来JVM配套的那些调试和监控工具可能就用不了了,包括我们熟知的jstack,jmap,不过Graalvm也提供了一些替代的方式帮助我们对native应用进行观测和调试,这部分大家可以下来自己看一下,这里就不展开了

  • GC策略支持有限 目前版本的GraalVM仅支持Serial GC和G1两种垃圾回收器,而且社区版本还不支持G1,只有企业版能用。Serial GC就是串行回收,是Java比较早期的垃圾回收策略,虽然GraalVM中对其进行了优化,但是也仅推荐用于堆比较小的应用程序,不太适合大型应用。这个只能寄希望于Oracle后续能够将G1也下放给社区版了,大家显然也不太可能去购买企业版本。不过好消息是Oracle前不久刚刚出了一个GraalVM的闭源免费版本(Oracle GraalVM 区别于开源版本的 GraalVM CE ),特性完全和企业版是一样的,免费使用,只是不开源,有兴趣的同学可以去研究下它的license。

在这里插入图片描述
欢迎关注我的公号—飞空之羽的技术手札,有深度的技术好文~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/37777.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【大数据开发语言Scala的入门教程】

🎥博主:程序员不想YY啊 💫CSDN优质创作者,CSDN实力新星,CSDN博客专家 🤗点赞🎈收藏⭐再看💫养成习惯 ✨希望本文对您有所裨益,如有不足之处,欢迎在评论区提出…

Python现在可以在线编程了!

你好,我是郭震 1 在线编程 在线编程好处: 1 无需安装和配置环境: 在线编程平台不需要用户在本地安装任何软件或配置开发环境。这对初学者和那些希望快速上手进行编程的人非常有利。 2 跨平台兼容性: 这些平台可以在任何具有互联网连接的设备上使用&#…

Flutter循序渐进==>Dart之类型、控制流和循环

导言 磨刀不误砍柴工,想搞好Flutter,先学好Flutter,还是本着我学Python的方法,先从数据类型、控制流和循环开始,这是每一种编程语言必用的。编程语言是相通的,基本精通一种后,学其它的就变得很…

捡到AI系统,金曲创作全靠玩

前言 毫无疑问,AI的发展已经在音乐领域带来了诸多变化和影响.但人类创作仍然具有不可替代的重要性。人类的灵感、创造力以及对音乐的深刻理解和情感表达是音乐产业的核心动力来源。AI 更倾向于被视为一种辅助工具,与人类创作者相互协作和融合,共同推动音…

laravel Dcat Admin 入门应用(七)列copyable和自定义copy

laravel Dcat Admin 入门应用(七)列copyable和自定义copy Dcat Admin 是一个基于 Laravel-admin 二次开发而成的后台构建工具,只需很少的代码即可构建出一个功能完善的高颜值后台系统。支持页面一键生成 CURD 代码,内置丰富的后台…

【机器学习300问】136、C4.5虽然改善了ID3决策树算法的部分缺点,但还是有不足,请问还有更好的算法吗?CART算法构建决策树

一、C4.5算法仍存在的不足 (1)计算效率不高 C4.5使用的信息增益率计算涉及熵的对数计算,特别是当属性值数量大时,计算成本较高。 (2)处理连续数值属性不够高效 ID3算法只能处理离散属性,需要预…

kafka学习笔记08

Springboot项目整合spring-kafka依赖包配置 有这种方式,就是可以是把之前test里的配置在这写上,用Bean注解上。 现在来介绍第二种方式: 1.添加kafka依赖: 2.添加kafka配置方式: 编写代码发送消息: 测试: …

c++11、14多线程从原理到线程池

c11、14多线程从原理到线程池 一.初识二.std::thread对象生命周期和线程等待与分离1.主线程不退出,thread对象被销毁,子线程仍然在运行。2.主线程阻塞,等待子线程退出3.子线程与主线程分离(守护线程) 三.线程创建的多种…

Nuxtjs3教程

起步 官方文档 官方目录结构 安装 npx nuxi@latest init <project-name>后面跟着提示走就行 最后yarn run dev 启动项目访问localhost:3000即可 路由组件 app.vue为项目根组件 <nuxt-page />为路由显示入口 将app.vue更改内容如下 <template><d…

C语言的数据结构:树与二叉树(哈夫曼树篇)

前言 上篇讲完了二叉树&#xff0c;二叉树的查找性能要比树好很多&#xff0c;如平衡二叉树保证左右两边节点层级相差不会大于1&#xff0c;其查找的时间复杂度仅为 l o g 2 n log_2n log2​n&#xff0c;在两边层级相同时&#xff0c;其查找速度接近于二分查找。1w条数据&am…

什么是中断?---STM32篇

目录 一&#xff0c;中断的概念 二&#xff0c;中断的意义 三&#xff0c;中断的优先级 四&#xff0c;中断的嵌套 如果一个高优先级的中断发生&#xff0c;它会立即打断当前正在处理的中断&#xff08;如果其优先级较低&#xff09;&#xff0c;并首先处理这个高优…

uniapp+php开发的全开源多端微商城完整系统源码.

uniappphp开发的全开源多端微商城完整系统源码. 全开源的基础商城销售功能的开源微商城。前端基于 uni-app&#xff0c;一端发布多端通用。 目前已经适配 H5、微信小程序、QQ小程序、Ios App、Android App。 采用该资源包做商城项目&#xff0c;可以节省大量的开发时间。 这…

周边美食小程序系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;美食店铺管理&#xff0c;菜品分类管理&#xff0c;标签管理&#xff0c;菜品信息管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;美食店铺&#…

基于SSM+Jsp的疫情居家办公OA系统

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…

20-OWASP top10--XXS跨站脚本攻击

目录 什么是xxs&#xff1f; XSS漏洞出现的原因 XSS分类 反射型XSS 储存型XSS DOM型 XSS XSS漏洞复现 XSS的危害或能做什么&#xff1f; 劫持用户cookie 钓鱼登录 XSS获取键盘记录 同源策略 &#xff08;1&#xff09;什么是跨域 &#xff08;2&#xff09;同源策略…

容易涨粉的视频素材有哪些?容易涨粉的爆款短素材库网站分享

如何挑选社交媒体视频素材&#xff1a;顶级视频库推荐 在社交媒体上脱颖而出&#xff0c;视频素材的选择至关重要。以下是一些顶级的视频素材网站推荐&#xff0c;不仅可以提升视频质量&#xff0c;还能帮助你吸引更多粉丝。 蛙学网&#xff1a;创意的源泉 作为创意和独特性的…

Databend db-archiver 数据归档压测报告

Databend db-archiver 数据归档压测报告 背景准备工作Create target databend table启动 small warehouse准备北京区阿里云 ECSdb-archiver 的配置文件准备一亿条源表数据开始压测 背景 本次压测目标为使用 db-archiver 从 MySQL 归档数据到 Databend Cloud&#xff0c; 归档的…

【王佩丰 Excel 基础教程】第一讲:认识Excel

文章目录 前言一、Excel软件简介1.1、历史上的其他数据处理软件与 Microsoft Excel1.2、Microsoft Excel 能做些什么1.3、Excel 界面介绍 二、Microsoft Excel 的一些重要概念2.1、Microsoft Excel 的几种常见文件类型2.2、工作簿、工作表、单元格. 三、使用小工具&#xff1a;…

Python_Socket

Python Socket socket 是通讯中的一种方式&#xff0c;主要用来处理客户端与伺服器端之串连&#xff0c;只需要protocol、IP、Port三项目即可进行网路串连。 Python套件 import socketsocket 常用函式 socket.socket([family], [type] , [proto] ) family: 串接的类型可分为…

Java中的Checked Exception和Unchecked Exception的区别

在Java中&#xff0c;异常分为两大类&#xff1a;已检查异常&#xff08;Checked Exception&#xff09;和未检查异常&#xff08;Unchecked Exception&#xff09;。 已检查异常是在编译时必须被捕获或声明的异常。换句话说&#xff0c;如果你的方法可能会抛出某个已检查异常&…