{"id":1090,"date":"2020-05-29T13:42:57","date_gmt":"2020-05-29T11:42:57","guid":{"rendered":"https:\/\/www.loicmathieu.fr\/wordpress\/?p=1090"},"modified":"2020-05-29T13:51:18","modified_gmt":"2020-05-29T11:51:18","slug":"quarkus-jlink-et-application-class-data-sharing-appcds","status":"publish","type":"post","link":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/quarkus-jlink-et-application-class-data-sharing-appcds\/","title":{"rendered":"Quarkus, jlink and Application Class Data Sharing (AppCDS)"},"content":{"rendered":"<p>Quarkus is optimized to start quickly and have a very small memory footprint. This is true when deploying in a standard JVM but even more so when deploying our application as a native executable via GraalVM.<\/p>\n<p>Quarkus greatly facilitates the creation of a native executable, thanks to this, a Quarkus application starts in a few tens of milliseconds and with a very small memory footprint: a few tens of MB of RSS (Resident Set Size &#8211; total memory usage of Java the process seen by the OS).<\/p>\n<p>If we take the comparison available on the graph below, we go, for a classic REST JSON \/ Hibernate stack, from 2s of start-up time to 42ms and from 145 MB of RSS to 28 MB. And only with a single core ! As an example, my application<code>bookmark-service<\/code> of my Quarkus Dojo <a href=\"https:\/\/github.com\/loicmathieu\/bookmarkit\" target=\"_blank\" rel=\"noopener noreferrer\">bookmarkit<\/a> starts in 1,2s on my laptop.<\/p>\n<img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" class=\"alignnone size-large wp-image-1091\" src=\"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/quarkus_metrics_graphic_bootmem_wide.png?resize=640%2C296&#038;ssl=1\" alt=\"\" width=\"640\" height=\"296\" srcset=\"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/quarkus_metrics_graphic_bootmem_wide.png?resize=1024%2C473&amp;ssl=1 1024w, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/quarkus_metrics_graphic_bootmem_wide.png?resize=300%2C139&amp;ssl=1 300w, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/quarkus_metrics_graphic_bootmem_wide.png?resize=768%2C355&amp;ssl=1 768w, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/quarkus_metrics_graphic_bootmem_wide.png?resize=1536%2C710&amp;ssl=1 1536w, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/quarkus_metrics_graphic_bootmem_wide.png?resize=2048%2C946&amp;ssl=1 2048w, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/quarkus_metrics_graphic_bootmem_wide.png?resize=584%2C270&amp;ssl=1 584w, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/quarkus_metrics_graphic_bootmem_wide.png?w=1280&amp;ssl=1 1280w, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/quarkus_metrics_graphic_bootmem_wide.png?w=1920&amp;ssl=1 1920w\" sizes=\"auto, (max-width: 640px) 100vw, 640px\" \/>\n<p>Another point not mentioned, is the size of the Docker image. A native application will only contain the code used by the application (thanks to the dead code elimination of GraalVM), as well as a minimalist JVM (SubstrateVM), and will therefore be smaller than a standard image which embeds all the third parties libraries as well as a full JVM.<\/p>\n<p>However, deploying your Quarkus application as a native application has a few drawbacks:<\/p>\n<ul><li>Because of the <em>close world assumption<\/em> of GraalVM, all the libraries used must be GraalVM compatible. This is guaranteed for Quarkus extensions, but for others, it&#8217;s up to you to check, and there can sometimes be unpleasant surprises.<\/li>\n\n<li>SubstrateVM is a partial JVM, it does not support JMX or JVM-TI (and therefore no Java agents), which can greatly complicate monitoring and administration of your application.<\/li>\n\n<li>A native application has peak performance that is lower than an application deployed in the JVM. Mainly because the Java Just-In-Time Compiler (JIT) will profile your application during its use and will therefore be able to make more relevant optimizations than GraalVM which performs these optimizations during compilation (Ahead-Of-Time compiler &#8211; AOT ).<\/li>\n<\/ul>\n<p>If you want to deploy your Quarkus application in a standard JVM, but optimize its startup time and the size of the Docker image, then <strong>Jlink<\/strong> and <strong>AppCDS<\/strong> can help you!<\/p>\n<p>In the following paragraphs, I will use the application <a href=\"https:\/\/github.com\/loicmathieu\/bookmarkit\/tree\/master\/bookmark-service\" target=\"_blank\" rel=\"noopener noreferrer\">bookmark-service<\/a> as example. A JVM at least 11 is necessary (I used OpenJDK 11.0.5).<\/p>\n<h2>Jlink<\/h2>\n<p><strong>Jlink<\/strong> allows to generate a custom JVM which will contain only the needed modules to make your application runnable and operationnal. This is made possible thanks to the modularization of the JDK which was made for Java 9 (<a href=\"https:\/\/openjdk.java.net\/projects\/jigsaw\/spec\/\" target=\"_blank\" rel=\"noopener noreferrer\">JSR-376<\/a> &#8211; projet Jigsaw). Who says reduced size of JVM, also says smaller Docker image!<\/p>\n<p><strong>Jlink<\/strong> takes as input the list of modules to include in your customized JVM. To know this list, you must use the tool <strong>jdeps<\/strong>.<\/p>\n<p>First of all, you must configure Quarkus to generate a fat jar (or uber jar) so that <strong> jdeps <\/strong> can analyze all the code of your application. To do this, add the following property to your <code>application.properties<\/code> :<\/p>\n<pre><code>quarkus.package.uber-jar=true<\/code><\/pre>\n<p>We then package the application via <code>mvn clean package<\/code>.<\/p>\n<p>Then we launch the <strong>jdeps<\/strong> command with the <code>--list-deps<\/code> option which allows you to list the modules on which your application depends.<\/p>\n<p>Here is the result of the <strong> jdeps <\/strong> command with the uber jar of the <code> bookmark-service<\/code> application:<\/p>\n<pre><code>$ jdeps --list-deps target\/bookmark-service-1.0-SNAPSHOT-runner.jar \n   JDK removed internal API\/org.relaxng.datatype\n   java.base\/sun.security.util\n   java.base\/sun.security.x509\n   java.compiler\n   java.datatransfer\n   java.desktop\n   java.instrument\n   java.logging\n   java.management\n   java.naming\n   java.rmi\n   java.security.jgss\n   java.security.sasl\n   java.sql\n   java.transaction.xa\n   java.xml\n   jdk.jconsole\n   jdk.management\n   jdk.unsupported\n<\/code><\/pre>\n<p>To create your custom JVM, you must use <strong>jlink<\/strong> by giving it the list of modules resulting from the call to the command <strong>jdeps<\/strong> via the option <code>--add-modules<\/code>. Small subtlety, the two modules <code>java.base\/sun.security.util<\/code> and <code>java.base\/sun.security.x509<\/code> must not be integrated, but we must integrate the <code>java.base<\/code> module instead. I don&#8217;t know where this result inconsistency of the <strong>jdeps<\/strong> command comes from, but the <code>java.base<\/code> module is used by any Java application, so it must always be present.<\/p>\n<p>For the <code>bookmark-service<\/code> application, here is the <strong>jlink<\/strong> command that I used:<\/p>\n<pre><code>$ jlink --no-header-files --no-man-pages --output target\/customjdk --compress=2 --strip-debug\\\n--module-path $JAVA_HOME\/jmods --add-modules java.base,java.base,java.base,java.compiler,\\\njava.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,\\\njava.rmi,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,\\\njava.xml,jdk.jconsole,jdk.management,jdk.unsupported\n<\/code><\/pre>\n<p>As I wanted to have the smallest possible JVM, I added the following options to the <strong> jlink <\/strong> command:<\/p>\n<ul><li><code>--no-header-files<\/code> : Do not include header files,<\/li>\n\n<li><code>--no-man-pages<\/code> : Do not include man pages,<\/li>\n\n<li><code>--compress=2<\/code> : ZIP level compression for compressible files,<\/li>\n\n<li><code>--strip-debug<\/code> : Do not include debug information.<\/li>\n<\/ul>\n<p>After that, we can use the JVM generated in the <code>target\/customjdk<\/code> directory to launch our application:<\/p>\n<pre><code>$ target\/customjdk\/bin\/java -Xmx32m -jar target\/bookmark-service-1.0-SNAPSHOT-runner.jar\n<\/code><\/pre>\n<p>If we compare the startup of our application with our custom JVM and the standard JVM, there is no advantage to RSS or startup time. The only interest here is the size of the JVM which went from 314MB to 51MB!<\/p>\n<p>Ideally, this should be automated in your Docker build via a multi-stage Dockerfile &#8230;<\/p>\n<p>To go further on <strong>jlink<\/strong>, I invite you to consult this article: <a href=\"https:\/\/medium.com\/azulsystems\/using-jlink-to-build-java-runtimes-for-non-modular-applications-9568c5e70ef4\" target=\"_blank\" rel=\"noopener noreferrer\">Using jlink to build java runtimes for non modular applications<\/a>.<\/p>\n<h2>AppCDS &#8211; Application Class Data Sharing<\/h2>\n<p>Warning: if you want to use <strong>AppCDS<\/strong> with <strong>jlink<\/strong>, you will have to use your custom JVM in the commands of this paragraph instead of your default JVM.<\/p>\n<p>One of the reasons for the consequent start-up time of a Java application is that, for each class, the JVM must load it from disk, verify it, then create a specific data structure (class metadata).<\/p>\n<p>Application Class Data Sharing (AppCDS) is a functionality with which we can create an archive of the metadata of our classes, to avoid doing it at startup. Once these metadatas are loaded, there is no difference in behavior with an application not using AppCDS.<\/p>\n<p>There are three steps for using <strong>AppCDS<\/strong>:<\/p>\n<p><strong>Step 1<\/strong>: Creating the list of classes to archive.<\/p>\n<pre><code>java -XX:DumpLoadedClassList=target\/classes.lst -jar target\/bookmark-service-1.0-SNAPSHOT-runner.jar\n<\/code><\/pre>\n<p>I stopped the application right after launching it, but we can imagine using it a little longer (launching a functional test for example) to make sure that all the code of the application has been called, and therefore as many classes as possible will be listed.<\/p>\n<p><strong>Step 2<\/strong>: Generating the archive with the JVM option <code>-Xshare:dump<\/code> which asks <strong>AppCDS<\/strong> to create it.<\/p>\n<pre><code>java -Xshare:dump -XX:SharedClassListFile=target\/classes.lst -XX:SharedArchiveFile=target\/app-cds.jsa --class-path target\/bookmark-service-1.0-SNAPSHOT-runner.jar\n<\/code><\/pre>\n<p>This command does not launch the application. It will generate a 69MB <code>app-cds.jsa<\/code> file that we can then use to launch our application.<\/p>\n<p><strong>Step 3<\/strong>: We launch the application by passing it the created archive as an argument.<\/p>\n<pre><code>java -XX:SharedArchiveFile=target\/app-cds.jsa -jar target\/bookmark-service-1.0-SNAPSHOT-runner.jar\n<\/code><\/pre>\n<p>With an archive, the application launches in 500ms instead of 1.2s, which makes a <strong>-60% startup time<\/strong>!<\/p>\n<p>Unfortunately, there is no difference in terms of memory footprint.<\/p>\n<p>For further reading: <a href=\"https:\/\/blog.codefx.org\/java\/application-class-data-sharing\/\" target=\"_blank\" rel=\"noopener noreferrer\">Application Class Data Sharing<\/a>.<\/p>\n<h2>Conclusion<\/h2>\n<p><strong>Jlink<\/strong> allows you to greatly reduce the size of your Docker image. If you deploy your application as a Docker container and you don&#8217;t necessarily control the node on which it is deployed (public cloud or private cloud shared with other applications), this can be interesting to limit the download time of your image. Be careful however to think about re-creating your custom JVM each time you add a new library because it could use a module absent from your previous custom JVM.<\/p>\n<p><strong>AppCDS<\/strong> seems very interesting to me, it greatly reduces the start-up time of your application without any inconvenience other than requiring the creation of the class archive beforehand. On the other hand, the creation of this archive is not trivial, but given the optimization (-60%) it&#8217;s worth it!<\/p>\n<p>Special thanks to Logan for proofreading and correcting the many spelling mistakes \ud83d\ude09<\/p>","protected":false},"excerpt":{"rendered":"<p>Quarkus is optimized to start quickly and have a very small memory footprint. This is true when deploying in a standard JVM but even more so when deploying our application as a native executable via GraalVM. Quarkus greatly facilitates the creation of a native executable, thanks to this, a Quarkus application starts in a few tens of milliseconds and with a very small memory footprint: a few tens of MB of RSS (Resident Set Size &#8211; total memory usage of&#8230;<p class=\"read-more\"><a class=\"btn btn-default\" href=\"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/quarkus-jlink-et-application-class-data-sharing-appcds\/\"> Read More<span class=\"screen-reader-text\">  Read More<\/span><\/a><\/p><\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"activitypub_content_warning":"","activitypub_content_visibility":"","activitypub_max_image_attachments":4,"activitypub_interaction_policy_quote":"anyone","activitypub_status":"","footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[9],"tags":[184,11,185,183,159,167],"class_list":["post-1090","post","type-post","status-publish","format-standard","hentry","category-informatique","tag-appcds","tag-java","tag-jdeps","tag-jlink","tag-performance","tag-quarkus"],"aioseo_notices":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":1345,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/quarkus-et-les-google-cloud-functions\/","url_meta":{"origin":1090,"position":0},"title":"Quarkus and  the Google Cloud Functions","author":"admin","date":"Tuesday November  2nd, 2021","format":false,"excerpt":"Quarkus is a microservice framework designed for the cloud and the containers. It is designed to have a reduced memory usage and the shortest possible startup time. It is mainly based on standards (Jakarta EE, Eclipse MicroProfile, ...) and allows the use of mature and widespread Java libraries via its\u2026","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2089,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/deploy-a-quarkus-application-in-cloud-run\/","url_meta":{"origin":1090,"position":1},"title":"Deploy a Quarkus application in Cloud Run","author":"admin","date":"Tuesday December 30th, 2025","format":false,"excerpt":"Quarkus is a microservice development framework designed for the cloud and containers. It is designed to have reduced memory usage and the shortest possible startup time. It is mainly based on standards (Jakarta EE, Eclipse MicroProfile, etc.) and allows the use of mature and widely used Java libraries via its\u2026","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1440,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/google-cloud-functions-2nd-gen\/","url_meta":{"origin":1090,"position":2},"title":"Google Cloud Functions 2nd gen","author":"admin","date":"Tuesday March 29th, 2022","format":false,"excerpt":"Sorry, this entry is only available in Fran\u00e7ais.","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/cloud-function-gen2-instances.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/cloud-function-gen2-instances.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/cloud-function-gen2-instances.png?resize=525%2C300&ssl=1 1.5x"},"classes":[]},{"id":1005,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/quarkus-et-testcontainers\/","url_meta":{"origin":1090,"position":3},"title":"Quarkus and Testcontainers","author":"admin","date":"Monday February 17th, 2020","format":false,"excerpt":"If you did not know Quarkus, here is an introductory article: Zoom sur Quarkus (french). Quarkus offers Unit Test (TU) support with JUnit 5 via the @QuarkusTest annotation, documentation for Quarkus TU support can be found here. Here is an example of a TU from the Hibernate ORM Quickstart :\u2026","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1960,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/creer-un-chatbot-avec-google-gemini-vertex-ai-et-quarkus\/","url_meta":{"origin":1090,"position":4},"title":"Creating a chatbot with Google Gemini Vertex AI and Quarkus","author":"admin","date":"Friday June 27th, 2025","format":false,"excerpt":"I recently created a Quarkus extension that provides access to Google Vertex AI. In this article, I'm going to use this extension to create a chatbot. The first step is to create a Quarkus project containing the REST and Google Cloud Vertex AI extensions. Here are the extensions to add\u2026","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/Capture-decran-du-2025-06-27-14-22-26-1024x376.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/Capture-decran-du-2025-06-27-14-22-26-1024x376.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/Capture-decran-du-2025-06-27-14-22-26-1024x376.png?resize=525%2C300&ssl=1 1.5x"},"classes":[]},{"id":1560,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/quarkus-tip-tester-une-fonction-google-cloud\/","url_meta":{"origin":1090,"position":5},"title":"Quarkus Tip: Testing a Google Cloud function","author":"admin","date":"Thursday December 29th, 2022","format":false,"excerpt":"I recently contributed a PR to Quarkus that contains a testing framework for Google Cloud functions. Quarkus supports creating Google Cloud functions three different ways: Using the Google Cloud API. Using a Quarkus HTTP extension: RESTEasy, Reactive routes, Servlet, Spring Web. Using Funqy, the cloud provider agnostic Quarkus function API.\u2026","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/posts\/1090","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/comments?post=1090"}],"version-history":[{"count":0,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/posts\/1090\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/media?parent=1090"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/categories?post=1090"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/tags?post=1090"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}