Shrink Your Java Apps by 90%: Cloud Foundry's Native Image Magic

Nicky Pike2024-10-257 min read

Cost savings can often be as simple as upgrading the version of Java you're using. However, anyone who has managed Java in large organizations knows that this process is rarely easy. This is where platforms like Cloud Foundry truly excel. In episode 21 of Cloud Foundry Weekly, Mark Fynes demonstrated how Tanzu Platform's Java Native Image Compilation can significantly reduce memory consumption, enabling new opportunities for cloud-native applications, even at the edge.

Shrink Your Java App's Memory by 90%

With GraalVM and the new Java native image buildpack on Tanzu Platform, you can reduce your Java app's memory usage by 90%. This allows your applications to use fewer resources, which directly contributes to cost savings and more compute capacity. Mark Fynes, Field Principal Solutions Engineer at Broadcom, walks us through this optimization with a simple helloWorld app that cuts memory from 1GB to just 32MB. For real-world apps, even halving memory needs would deliver huge benefits, saving costs, capacity expansion, and enhancing scalability. So how can you implement this with existing applications currently running on a tPCF foundation?

The Java Optimizer Solution

Let's say we have an existing Java app, testApp. The first thing we need to do is re-build the app as a Java native image. Mark's CF Java Optimizers simplifies this process. We start by targeting the space in which the application is currently deployed and execute the command:

bash
cf-java-optimizer.sh <appName> <appMemorySize>

Here, the optional "AppMemory" parameter specifies the amount of memory desired for the application post build, with a default value of 32Mbs. The recommendation is to start with your original application's memory footprint, build the application, then adjust based on running memory once deployed. You should see significant memory requirement reduction upon migration to native images. While Mark's demo gives a better visual, this method will, in essence, extract the currently running application droplet, reissue the build command (while leaving the current app running) to migrate the droplet into a native image, then redeploy in a typical blue green fashion.

You'll see in Mark's demo his CF Java Optimizer script automates migrating existing apps to Java native images—bringing sub-second startup times, improved resource use, and no downtime—without developer interaction or application downtime. This appeals to developers and platform engineers alike, and it will appeal even more to management that's looking to lower costs. Mark also provides the demo code for those of you who like to experiment, this is a great way to show the art of the possible.

Should you decide to replicate this demo, it is important to include the -Pnative flag in your Maven package and ensure the graalvm-build-tools is included in your POM file to accommodate the change necesssary to complete the native image compliation.

Watch Mark's demo here:

A Small Price for Big Gains

One downside to native images is that they take longer to compile, sometimes more than 20 minutes, but as Mark explains, it's a one-time cost that can be implemented in staging and production builds. To deal with this, developers can use non-native images that are less time consuming during experimentation and testing, then switch to native images for staging to realize the memory optimizations. Once built, the droplet can be easily reused across multiple Cloud Foundry implementations, on-prem or public cloud. That is, you only need to build the droplet once and then you can deploy it multiple times and places, just like container images.

Utilizing some less known commands on the Tanzu Platform cf-cli, you can deploy droplets across orgs, spaces, and Cloud Foundry environments, to enhance deployment flexibility. In our example HelloWorld app, the below shows the commands necessary, how to list, download, and deploy the original testApp droplet as testApp2:

cf droplets <AppName>: Lists all stored droplets for the specified app

bash
repo/sandbox  cf droplets testApp

Getting droplets of app testApp in org DemoOrg / space DemoSpace as admin...

guid                                             state    created
23efa083-d63c-448e-b405-d1f641d5a66d (current)   staged   Thu 19 Sep 10:12:22 MDT 2024
589a31f6-dce5-44a5-82f9-94c89565ca57             staged   Thu 19 Sep 09:58:01 MDT 2024
fbbb3869-1e42-485d-9030-2d6e6e621215             staged   Thu 19 Sep 09:50:40 MDT 2024

cf download-droplet <AppName> <optional_switches>: This command downloads a previously compiled droplet from Tanzu Platform as a .tgz file.

–droplet <GUID>: Specify the app's droplet GUID to download. Defaults to the current droplet of the named applications.

–path, -p <directory>: Set the destination path. Defaults to the current directory if not specified.

bash
repo/sandbox  cf download-droplet testApp --path ~/tmp

Downloading current droplet for app GuidEnricher in org DemoOrg / space DemoSpace as admin...

Droplet downloaded successfully at /Users/<user>/tmp/droplet_23efa083-d63c-448e-b405-d1f641d5a66d.tgz

OK

cf push <appName> –droplet <path_to_downloaded_droplet>: Deploy a previously downloaded droplet to another foundation, org, or space. This allows you to build the droplet once and deploy it multiple times without rebuilding.

bash
repo/sandbox cf push testApp2 --droplet ~/tmp/droplet_23efa083-d63c-448e-b405-d1f641d5a66d.tgz

Pushing app testApp2 to org DemoOrg / space DemoSpace as admin...

Uploading droplet bits...

 79.80 MiB / 79.80 MiB [===========================================] 100.00% 32s

Waiting for API to complete processing files...

Waiting for app testApp2 to start...

Instances starting...

Instances starting...

name:              testApp2
requested state:   started
routes:            testApp2.<redacted_domain>
last uploaded:     Tue 24 Sep 15:09:28 MDT 2024
stack:            
buildpacks:       
type:           web
sidecars:       
instances:      1/1
memory usage:   1024M
     state     since                  cpu      memory         disk         details
#0   running   2024-09-24T21:10:21Z   104.8%   119.3M of 1G   206M of 1G

That's it! With a change like this, you can expect anywhere from 30% to 90% reduced memory usage. That's a lot for even just one app, when spread across hundreds of apps you can imagine how it really adds up.

More than just cost savings

Using fewer resources and saving money is probably the best result in most cases. But, there's other functionality you can get when you switch to Java native image buildpacks:

  • Build-Only Foundations: Build droplets once and deploy them across multiple environments using CI/CD pipelines, ensuring identical deployments.
  • Edge Deployments: Ideal for lightweight Cloud Foundry implementations in resource-constrained environments.
  • Multi-Cloud Portability: Use small footprint foundations in public clouds, reducing runtime costs and management complexity while enabling business units to pay for only what they use.

Java native image compilation and Cloud Foundry droplet portability have the potential to transform how apps are deployed in the cloud and at the edge. These capabilities provide the ability to optimize resource usage, cut costs, and scale applications efficiently. Whether you're a developer, platform engineer, or business leader, these advancements can provide you with significant value.