发布时间:2025-12-10 11:24:59 浏览次数:11
本人资历、水平有限,如有错误请大佬们批评指正,谢谢!
平时 Java 用得比较多,也会用 Golang 或者 C 等静态编译语言的人,应该多少都会感觉到,即使是最简单的 hello, world,Java 程序的启动速度相比静态编译出的可执行程序会慢上一拍。
如果使用 Java 等语言实现的运行在 JVM 上的 CLI 工具,这种慢一拍的感觉应该会更明显(使用过 RocketMQ 的 mqadmin 脚本的同学应该有所体会)。
Spring Framework 全家桶的启动就更不用说了,大项目启动时间可达 分钟 级别……
先来个简单的实验:
一段 Java 的 hello, world
public class Main {public static void main(String[] args) {System.out.println("hello, java");}}一段 Golang 的 hello, world
package mainfunc main() {println("hello, go")}➜ ~ go build main.go➜ ~ repeat 10 time ./mainhello, go./main 0.00s user 0.00s system 107% cpu 0.001 totalhello, go./main 0.00s user 0.00s system 118% cpu 0.001 totalhello, go./main 0.00s user 0.00s system 110% cpu 0.001 totalhello, go./main 0.00s user 0.00s system 109% cpu 0.001 totalhello, go./main 0.00s user 0.00s system 112% cpu 0.001 totalhello, go./main 0.00s user 0.00s system 119% cpu 0.001 totalhello, go./main 0.00s user 0.00s system 109% cpu 0.001 totalhello, go./main 0.00s user 0.00s system 108% cpu 0.001 totalhello, go./main 0.00s user 0.00s system 106% cpu 0.001 totalhello, go./main 0.00s user 0.00s system 107% cpu 0.001 total➜ ~ javac Main.java ➜ ~ repeat 10 time java Mainhello, javajava Main 0.07s user 0.04s system 119% cpu 0.087 totalhello, javajava Main 0.07s user 0.02s system 115% cpu 0.074 totalhello, javajava Main 0.06s user 0.01s system 114% cpu 0.063 totalhello, javajava Main 0.06s user 0.01s system 146% cpu 0.046 totalhello, javajava Main 0.06s user 0.01s system 147% cpu 0.049 totalhello, javajava Main 0.05s user 0.02s system 142% cpu 0.046 totalhello, javajava Main 0.05s user 0.01s system 143% cpu 0.045 totalhello, javajava Main 0.05s user 0.01s system 142% cpu 0.045 totalhello, javajava Main 0.05s user 0.02s system 144% cpu 0.045 totalhello, javajava Main 0.05s user 0.01s system 142% cpu 0.045 totalGolang 写的 hello, world 基本是 敲下键盘那一刻就已经运行完毕退出了,Java 写的 hello, world 运行时间人类可以感受到,尤其是冷启动(首次启动 JVM 进程,可能长达数百毫秒。在树莓派这类硬件上,JVM 冷启动可长达数秒)。
基于 JVM 的应用确实需要一定的启动时间(尤其是 Spring),启动内存相比原生应用也不低。“速度慢,吃内存” 成了大家黑 Java 的关键点。
不过,在目前常见的架构下,这些问题不会特别突出:
这么看来其实 “速度慢,吃内存” 也不是什么问题。
但是, 脱离场景谈技术都是耍流氓 。
对于一些用户群体比较确定、不会有突发流量,充分评估过资源需求、本身比较稳定的应用,启动时间和内存占用一般不会有什么问题。
但是对于弹性计算、存在突发流量等需要时刻准备水平扩容的场景,JVM 的短板就被放大了。
例如基于事件驱动的函数计算,按需分配资源,不用的时候不占用资源。不预留实例的函数计算,只有事件发生的时候才会分配资源并创建实例。
从以下三个方面谈 JVM 的短板:
在启动时间方面:
如果函数实例是后台计算等不涉及用户体验的操作,实例启动慢一点影响不会很大。
但如果函数提供的服务具有即时性、涉及用户体验、对时间敏感,实例启动时间就会在很大程度上影响着用户体验。原生应用和 JVM 相比,本身就没有启动的过程(本人的理解,从另一个角度说,操作系统的启动就是原生应用的启动)。因应用经过静态编译,业务逻辑的启动一般也不会占用太多时间(除非涉及 I/O 等对于 CPU 来说很慢的操作)。
在内存占用方面:
例如使用阿里云函数计算服务,内存需求关系到费用问题。一般虚拟机和原生应用相比在内存占用方面没有优势。一段相同逻辑的代码分别使用虚拟机和原生应用运行,虚拟机内存需求一般不会低于原生应用。
在运行期间:
JVM 有很多优化手段,基于 JVM 的应用经过时间的推移或者主动进行预热,可能会越来越快。但是,在弹性计算这类场景中,JVM 可能根本来不及优化,实例就已经被释放了。没有经过优化的 JVM 应用在运行速度上很难和原生应用的机器语言匹敌。
所以,回答标题的问题:
在现在这种新架构、新技术百花齐放的时代,如果 JVM 相关技术栈不能与时俱进,将很容易被后来者蚕食。
有条件的建议在 Linux 进行,Windows 上进行可能坑会稍多。
GraalVM 可以在 Oracle 官网下载:https://www.oracle.com/downloads/graalvm-downloads.html?selected_tab=20l
注意版本的选择!
国内网络环境下载速度可能较慢,此处提供本人在用的版本:
GraalVM 的安装使用和普通的 JDK 没有区别,下载后解压并设置环境变量就可以了。
GraalVM 相比普通的 OpenJDK / Oracle JDK 增加了一些命令,例如用于管理 GraalVM 组件的命令 gu。
不要忘记安装 Native Image 组件!可以通过 gu 命令安装提前下载好 Native Image 组件的 jar。
Native Image 组件安装文档:https://docs.oracle.com/en/graalvm/enterprise/20/guide/reference/native-image.html
环境基本准备好了!
使用 Native Image 可能还需要 gcc 等开发工具,各环境所需依赖可以参考 Quarkus 文档:
https://quarkus.io/guides/building-native-image#prerequisites
除了 Native Image,GraalVM 还有很多黑科技。篇幅有限,本文只关注 Native Image。
官网:https://www.graalvm.org/
无论是使用命令式还是响应式编程,都有相近的代码风格
命令式:
@InjectSayService say;@GET@Produces(MediaType.TEXT_PLAIN)public String hello() {return say.hello();}响应式:
@Inject @Channel("kafka")Publisher<String> reactiveSay;@GET@Produces(MediaType.SERVER_SENT_EVENTS)public Publisher<String> stream() {return reactiveSay;}Quarkus 不会让用户花费大量时间学习新技术,其编程模型建立在各种已有的标准之上,例如:
Quarkus 提供了一个类似于 Spring Initializr 的项目初始化方式:https://code.quarkus.io/
任选一种初始化方式:
以下操作基于 IntelliJ IDEA。
可选构建工具:
选择依赖:
项目初始化后且依赖下载完成后,可以使用 ./mvnw compile quarkus:dev 运行项目,或者通过 GUI:
Quarkus 启动:
在浏览器打开 http://localhost:8080/
项目初始化完成。
Quarkus 提供了 Spring 的兼容。
Order.java 节选,忽略 getter / setter:
package vip.wuweijie.hello.quarkus.entity;import javax.persistence.*;import java.io.Serializable;import java.sql.Timestamp;/*** @author wuweijie*/@Entitypublic class Order implements Serializable {@Idprivate Long id;@Column(insertable = false)private Timestamp orderTime;private String personName;private String orderType;private String remark;}OrderRepository.java:
package vip.wuweijie.hello.quarkus.repository;import org.springframework.data.jpa.repository.JpaRepository;import vip.wuweijie.hello.quarkus.entity.Order;/*** @author wuweijie*/public interface OrderRepository extends JpaRepository<Order, Long> {}OrderResource.java
package vip.wuweijie.hello.quarkus;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import vip.wuweijie.hello.quarkus.entity.Order;import vip.wuweijie.hello.quarkus.repository.OrderRepository;import java.util.List;/*** @author wuweijie*/@RestController@RequestMapping("/order")public class OrderResource {@Autowiredprivate OrderRepository orderRepository;@GetMappingpublic List<Order> getOrders() {return orderRepository.findAll();}@PostMappingpublic Order addOrder(@RequestBody Order order) {return orderRepository.saveAndFlush(order);}}请求:
GET http://localhost:8080/order###POST http://localhost:8080/orderContent-Type: application/json{"orderTime": 1591432276000,"personName": "POST","orderType": "nil","remark": "From Post"}先执行一次查询:
GET http://localhost:8080/orderHTTP/1.1 200 OKContent-Length: 287Content-Type: application/json[{"id": 1,"orderTime": 1591432276000,"personName": "烫烫烫","orderType": "None","remark": null},{"id": 2,"orderTime": 1591432311000,"personName": "锟斤拷","orderType": "Dinner","remark": "Double"}]执行一次插入,返回数据:
{"id":3,"orderTime":1591432276000,"personName":"POST","orderType":"nil","remark":"From Post"}再次查询数据:
GET http://localhost:8080/orderHTTP/1.1 200 OKContent-Length: 287Content-Type: application/json[{"id": 1,"orderTime": 1591432276000,"personName": "烫烫烫","orderType": "None","remark": null},{"id": 2,"orderTime": 1591432311000,"personName": "锟斤拷","orderType": "Dinner","remark": "Double"},{"id": 3,"orderTime": 1591434989319,"personName": "POST","orderType": "nil","remark": "From Post"}]如果检测到代码文件变化,则会重新加载代码:
2020-06-06 17:16:28,560 INFO [io.qua.dep.dev] (vert.x-worker-thread-1) Changed source files detected, recompiling [/home/sia/IdeaProjects/hello-quarkus/src/main/java/vip/wuweijie/hello/quarkus/entity/Order.java]2020-06-06 17:16:28,950 INFO [io.quarkus] (Quarkus Main Thread) hello-quarkus stopped in 0.030s__ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2020-06-06 17:16:29,130 INFO [io.qua.arc.pro.BeanProcessor] (build-26) Found unrecommended usage of private members (use package-private instead) in application beans:- @Inject field vip.wuweijie.hello.quarkus.OrderResource#orderRepository2020-06-06 17:16:29,220 INFO [io.agr.pool] (Quarkus Main Thread) Datasource '<default>': Initial size smaller than min. Connections will be created when necessary2020-06-06 17:16:29,258 INFO [io.quarkus] (Quarkus Main Thread) hello-quarkus 1.0-SNAPSHOT on JVM (powered by Quarkus 1.5.0.Final) started in 0.301s. Listening on: http://0.0.0.0:80802020-06-06 17:16:29,259 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.2020-06-06 17:16:29,259 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, jdbc-postgresql, mutiny, narayana-jta, resteasy, resteasy-jackson, spring-boot-properties, spring-data-jpa, spring-di, spring-web]2020-06-06 17:16:29,259 INFO [io.qua.dep.dev] (vert.x-worker-thread-1) Hot replace total time: 0.700s本示例 建议可用内存不小于 8GB ,依赖越多,构建过程所需内存越大。
执行命令
./mvnw package -Pnative执行日志:
➜ hello-quarkus git:(master) ✗ ./mvnw package -Pnative[INFO] Scanning for projects...[INFO] [INFO] --------------< vip.wuweijie.hello.quarkus:hello-quarkus >--------------[INFO] Building hello-quarkus 1.0-SNAPSHOT[INFO] --------------------------------[ jar ]---------------------------------[INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello-quarkus ---[INFO] Using 'UTF-8' encoding to copy filtered resources.[INFO] Copying 2 resources[INFO] [INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ hello-quarkus ---[INFO] Changes detected - recompiling the module![INFO] Compiling 4 source files to /home/sia/IdeaProjects/hello-quarkus/target/classes[INFO] [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello-quarkus ---[INFO] Using 'UTF-8' encoding to copy filtered resources.[INFO] skip non existing resourceDirectory /home/sia/IdeaProjects/hello-quarkus/src/test/resources[INFO] [INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ hello-quarkus ---[INFO] Changes detected - recompiling the module![INFO] Compiling 2 source files to /home/sia/IdeaProjects/hello-quarkus/target/test-classes[INFO] [INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ hello-quarkus ---[INFO] [INFO] -------------------------------------------------------[INFO] T E S T S[INFO] -------------------------------------------------------[INFO] Running vip.wuweijie.hello.quarkus.ExampleResourceTest2020-06-06 17:28:56,229 INFO [io.qua.arc.pro.BeanProcessor] (build-4) Found unrecommended usage of private members (use package-private instead) in application beans:- @Inject field vip.wuweijie.hello.quarkus.OrderResource#orderRepository2020-06-06 17:28:57,057 INFO [io.agr.pool] (main) Datasource '<default>': Initial size smaller than min. Connections will be created when necessary2020-06-06 17:28:57,446 INFO [io.quarkus] (main) Quarkus 1.5.0.Final on JVM started in 2.301s. Listening on: http://0.0.0.0:80812020-06-06 17:28:57,447 INFO [io.quarkus] (main) Profile test activated. 2020-06-06 17:28:57,447 INFO [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, jdbc-postgresql, mutiny, narayana-jta, resteasy, resteasy-jackson, spring-boot-properties, spring-data-jpa, spring-di, spring-web][INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.775 s - in vip.wuweijie.hello.quarkus.ExampleResourceTest2020-06-06 17:28:58,615 INFO [io.quarkus] (main) Quarkus stopped in 0.034s[INFO] [INFO] Results:[INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0[INFO] [INFO] [INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello-quarkus ---[INFO] Building jar: /home/sia/IdeaProjects/hello-quarkus/target/hello-quarkus-1.0-SNAPSHOT.jar[INFO] [INFO] --- quarkus-maven-plugin:1.5.0.Final:build (default) @ hello-quarkus ---[INFO] [org.jboss.threads] JBoss Threads version 3.1.1.Final[INFO] [org.hibernate.Version] HHH000412: Hibernate ORM core version 5.4.16.Final[INFO] [io.quarkus.arc.processor.BeanProcessor] Found unrecommended usage of private members (use package-private instead) in application beans:- @Inject field vip.wuweijie.hello.quarkus.OrderResource#orderRepository[INFO] [io.quarkus.deployment.pkg.steps.JarResultBuildStep] Building native image source jar: /home/sia/IdeaProjects/hello-quarkus/target/hello-quarkus-1.0-SNAPSHOT-native-image-source-jar/hello-quarkus-1.0-SNAPSHOT-runner.jar[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] Building native image from /home/sia/IdeaProjects/hello-quarkus/target/hello-quarkus-1.0-SNAPSHOT-native-image-source-jar/hello-quarkus-1.0-SNAPSHOT-runner.jar[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] Running Quarkus native-image plugin on GraalVM Version 20.1.0 (Java Version 11.0.7)[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] /usr/local/graalvm-ee-java11-20.1.0/bin/native-image -J-Dsun.nio.ch.maxUpdateArraySize=100 -J-Djava.util.logging.manager=org.jboss.logmanager.LogManager -J-DCoordinatorEnvironmentBean.transactionStatusManagerEnable=false -J-Dvertx.logger-delegate-factory-class-name=io.quarkus.vertx.core.runtime.VertxLogDelegateFactory -J-Dvertx.disableDnsResolver=true -J-Dio.netty.leakDetection.level=DISABLED -J-Dio.netty.allocator.maxOrder=1 -J-Duser.language=en -J-Dfile.encoding=UTF-8 --initialize-at-build-time= -H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime -H:+JNI -jar hello-quarkus-1.0-SNAPSHOT-runner.jar -H:FallbackThreshold=0 -H:+ReportExceptionStackTraces -H:-AddAllCharsets -H:-IncludeAllTimeZones -H:EnableURLProtocols=http,https --enable-all-security-services -H:NativeLinkerOption=-no-pie --no-server -H:-UseServiceLoaderFeature -H:+StackTrace hello-quarkus-1.0-SNAPSHOT-runner-H:IncludeAllTimeZones and -H:IncludeTimeZones are now deprecated. Native-image includes all timezonesby default.[hello-quarkus-1.0-SNAPSHOT-runner:9103] classlist: 6,932.73 ms, 1.19 GB[hello-quarkus-1.0-SNAPSHOT-runner:9103] (cap): 541.19 ms, 1.68 GB[hello-quarkus-1.0-SNAPSHOT-runner:9103] setup: 2,467.68 ms, 1.68 GB17:29:11,362 INFO [org.hib.Version] HHH000412: Hibernate ORM core version 5.4.16.Final17:29:11,367 INFO [org.hib.ann.com.Version] HCANN000001: Hibernate Commons Annotations {5.1.0.Final}17:29:11,384 INFO [org.hib.dia.Dialect] HHH000400: Using dialect: io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect17:29:23,231 INFO [org.jbo.threads] JBoss Threads version 3.1.1.FinalWARNING GR-10238: VarHandle for static field is currently not fully supported. Static field private static volatile java.lang.System$Logger jdk.internal.event.EventHelper.securityLogger is not properly marked for Unsafe access![hello-quarkus-1.0-SNAPSHOT-runner:9103] (clinit): 873.00 ms, 5.41 GB[hello-quarkus-1.0-SNAPSHOT-runner:9103] (typeflow): 21,272.04 ms, 5.41 GB[hello-quarkus-1.0-SNAPSHOT-runner:9103] (objects): 26,923.87 ms, 5.41 GB[hello-quarkus-1.0-SNAPSHOT-runner:9103] (features): 1,099.55 ms, 5.41 GB[hello-quarkus-1.0-SNAPSHOT-runner:9103] analysis: 53,824.27 ms, 5.41 GB[hello-quarkus-1.0-SNAPSHOT-runner:9103] universe: 1,649.18 ms, 5.41 GB[hello-quarkus-1.0-SNAPSHOT-runner:9103] (parse): 4,248.05 ms, 5.59 GB[hello-quarkus-1.0-SNAPSHOT-runner:9103] (inline): 2,953.33 ms, 5.79 GB[hello-quarkus-1.0-SNAPSHOT-runner:9103] (compile): 61,356.01 ms, 7.23 GB[hello-quarkus-1.0-SNAPSHOT-runner:9103] compile: 72,475.76 ms, 7.23 GB[hello-quarkus-1.0-SNAPSHOT-runner:9103] image: 5,032.36 ms, 7.23 GB[hello-quarkus-1.0-SNAPSHOT-runner:9103] write: 995.36 ms, 7.23 GB[hello-quarkus-1.0-SNAPSHOT-runner:9103] [total]: 143,635.77 ms, 7.23 GB[INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 145589ms[INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ------------------------------------------------------------------------[INFO] Total time: 02:34 min[INFO] Finished at: 2020-06-06T17:31:25+08:00[INFO] ------------------------------------------------------------------------构建完成后,target 目录下多了一个可执行文件。
执行后,可以看到应用的启动时间只需要 0.043s,基于 JVM 启动需要 1.730s。
GraalVM 相关:
Quarkus 相关: