Build Time Boot: Refine Java Framework
In this article, explore a new trend of Java Framework: “Build Time Boot” techniques, which make java applications slim and run faster.
Join the DZone community and get the full member experience.Join For Free
Innovation never stopped in Java land. In this article, I am going to explore a new trend of Java Framework: “Build Time Boot” techniques, which make java applications slim and run faster.
If you searched the microservice framework, you will find a lot of options like Spring Boot, Vertx, Quarkus, Micronaut, Helidon (those are all java), GoMicro (Go), Molecular (nodejs), etc.
Do you know why so many Java framework options?
Java is a 25 years old language, so the landscape and market definitely are much larger than other languages. It shapes the framework landscapes and gives you a lot of choices in the ecosystem.
What Is Build Time Boot?
This is an interesting expression. People might think it is insane to say boot in build time if they don’t know the context.
What it really means is that moving tasks are usually done in the startup to the build phase.
Yes, actually this has become a popular technique for modern microservice frameworks like Quarkus, Micronauts.
Traditional Application Server Can’t Startup Fast
In tradition, Application Server was valued by heavy lifting a lot of common work, preparing the beans, classloading, preparing the context for java reflections, preparing thread pools, etc. So at that time, it makes sense for an application server to do it once and do it for all.
That’s the main reason application servers get bigger and slower. It’s not a problem but actually a good strategy since it’s serving as a common platform for a long-running phase and feeding lots of applications.
Time flies, under modernized application architecture, traditional application servers have been switching their role to serve only one microservice, it makes less and less sense to do a “lot” of pre-work before they are ready to serve since you only need to care about one service.
We want it faster and smaller, there will be no re-deploy, hot-deploy scenario. The big platform scenario disappears and a small runtime scenario arises.
Spring boot like java framework “make the jar, no wars” address most of these issues. Your application and application server now start at the time and run as one.
But it is still slow if you compare it to its neighbors like nodejs framework or golang framework. Spring Boot style application at least takes about 2+ seconds to start, and about 4 seconds if you count time to first response.
Let’s dig into some details and see what actually causes the problem and where and how the problem can improve.
First let’s set up our little experiment environment，make sure that we have some common and basic tasks for web applications such as using java annotation, CDI, etc.
You can find out the completed experiment code and log here, it includes both Quarkus and spring boot quickstart scenarios.
Claim: this experiment is only an attempt to compare how “Build Time Boot” changes the way we do java framework and application server. It‘s not to prove or argue which framework is the best. I use and like both spring boot and Quarkus. I picked spring boot and Quarkus as representative as a traditional and “modern-lize” framework but you can regard others like wildfly swarm, micronaut, etc as the same.
Classloading Too Many Things
There are too many things loaded. Have a peek at how many classes are loaded. Surprisingly the number is so large even for popular lightweight java frameworks like “Spring Boot.”
Spring Boot load classes: 6022
Quarkus load classes: 3357
There are several reasons for the odds:
- Quarkus used a much more lightweight vertex as servlet core and Spring boot uses a heavier Tomcat as we all knew.
2. The framework itself for providing easy use and productivity is much lightweight，including CDI, Config handling, validation, servlet implementation, etc;
3. Another recipe here is Quarkus is doing a lot of “Built Time Boot” work in the build phase instead of the runtime phase. So it eliminates a lot of classes, which becomes unused in runtime.
Too Many Metadata Processing Tasks
There are well known common tasks a framework needs to uplift for developers.
- Parse configuration files like XML, yaml, JSON, etc
- Handle java annotations, classpath scan
- Handle CDI
- Java reflection, dynamic preparation, indexing, caching task.
Do they cost a lot of time?
Yes. Considering a real-world application depends on a lot of dependencies, preparing, parsing, and caching for tasks like annotation, bean creation, method, field indexing, etc. will snowball to a big size.
Have a look at a very simple spring boot application startup log (remember to open the “TRACE” level log). It’s not convenient to paste the log here since it’s too large, but you will see tons of metadata processing related work. Bear in mind this is an empty spring boot application. It will get much heavier for more complex applications.
On the contrary, you can’t find those tasks in the Quarkus bootstrap log.
Why? Does quarkus skip all those important tasks?
Not at all, it moves.
How to Build Time Boot Works
Summary of why traditional application servers startup slow at first:
- Too many class being loaded
- Too much metadata processing time for handling annotation, cdi, reflection, proxy, etc.;
Now let’s see how we can do differently in “Build Time Boot.”
The philosophy is simple, proceeding everything you could at build time and only leaving the have-to part invocation at runtime. For example, we could process the following in build time instead of runtime.
- Configuration can be parser and turn into byte code at build time.
- Java Annotation can be decomposed into a direct method call at build time
- CDI beans can be built up at build time.
- All the reflection, proxy behavior can be analyzed and turn into executable bytecode at build time.
But how? you might ask.
The answer is “Recording and Replay”
Recorded the Boot at the Build Time, Replay It at Runtime
If you check the quarkus build log, you can spot
[INFO] — — quarkus-maven-plugin:1.8.3.Final:build (default) @ quarkus-getting-started — -
[DEBUG] [io.quarkus.deployment.QuarkusAugmentor] Beginning Quarkus augmentation
Starting step …
Finished step …
Starting step …
Finished step …
What it does by those steps is “recording”, the “tapes” the quarkus recording is the runtime invocable bytecode so that the heavy lifting metadata processing, construction, optimization, etc. won’t be needed anymore in the runtime.
The job is done by quarkus-maven-plugin. It will mock the web application startup process in the build time phase and do all the metadata processing work as much as possible and output them as bytecode.
First, let’s have a view of what actually Quarkus is built and generated extra during this process, you can review the class list at https://github.com/ryanzhang/buildtimeboot/blob/main/quarkus-build-class.txt.
By comparison, here is the spring boot list: https://github.com/ryanzhang/buildtimeboot/blob/main/springboot-build-class.txt
Two things to notice:
1. Quarkus generated CDI Bean Class.
You can see _Bean.class and _ClientProxy.class. These classes are usually generated in runtime. But quarkus moves them into build time.
2. Quarkus generated a list of recorded bytecode. You can view it in quarkus-build-class.txt.
Let me show you the invocation part which calls the “recorded bytecode” at runtime.
So basically what quarkus do here:
- Recorded many metadata process tasks into bytecodes, so it can be loaded directly without computing them every time the application starts.
- Put them into a static or main method so they can be invoked directly without processing again. They appeared in the picture right.
Note: The recorded bytecode is under io/quarkus/deployment/steps/
The invocatio code path is io/quarkus/runner/ApplicationImpl.class
Also bear in mind the generated class can be much larger in a real-world application since it would depend on more libraries and frameworks such as hibernate, jdbc, etc.
Because bytecode manipulating and writing is a very complicated task, what Quarkus does is mock those “boot” tasks by splitting them into small build steps in the background thread in build time and using “proxy” to record those steps into byte code.
If you want to know more of the details on how the recording is done, here is a good reference doc.
By the way, you probably would notice that Quarkus intentionally doesn’t put lib/ folder into the runnable jar for container image optimization purposes since when you rebuilt the images so the same lib container image layer can be cached.
Gun and Bullet
So “Build Time Boot” in quarkus is implemented by plugins + extensions. A quarkus extension is an optimized jar that can make “recording” possible in the build time.
The maven/gradle plugin is the gun. And the quarkus extension is the bullet.
As long as you picked up the quarkus maven plugin and included the optimized jar. You don’t need to change anything you did for java programming. It’s still the old good taste. That means you can still use CDI, Java annotation, even java reflection if you have to.
(It’s wise to not use java reflection but in some scenarios, you can’t change the code already existed.)
So from a developer perspective, the bullet is very important. Otherwise, it will be like holding a best gun without a bullet.
Fortunately, there is a wide variety of popular frameworks and libraries already available for java development. You can find it at https://code.quarkus.io/
You can still use or mix the normal jar if they do not appear in the quarkus community. It should cause no problem but you would not likely have those optimized effects mentioned for the normal jar.
To summarize, I think Build Time Boot is a really great idea for the Java framework. It does all the heavy work for you and you don’t need to change the way you program. For the Java framework you can still enjoy the great experience of Java‘s taste, but don’t need to tolerate the inefficient running. You can have your cake and eat it too.
Published at DZone with permission of Ryan ZhangCheng. See the original article here.
Opinions expressed by DZone contributors are their own.