Java Virtual Machine is an abstraction layer that allows Java bytecode to run on any platforms. However, as one of the most widely used virtual machines in the world, developers frequently overlook it, missing opportunities for engineering improvement such as performance optimization.

Tier1 Service is the most impactful application services (and yes, there are Tier0 Services such as AWS S3) that Amazon owns, so Tier1 Service should be highly available. In most cases, (not surprisingly) Tier1 services are legacy services, imposing the technical challenge of how to maintain its availability while still being able to keep up with new requirements. Of course, a simple solution is to just migrate off the legacy system, but in reality, the risk of business disruption and other unforeseen hurdles are formidable.

Fortunately, JVM provides enough flexibility. In Tier1-Service JVM Series, we would like to share how we configure JVM for one of our Tier1 Services. Our stories are presented in a way that we hope readers can extrapolate and apply in many specific use cases.

Problem

One of the most common tech-debts almost every developer/organization have to deal with is to upgrade dependencies and the major rationales are very obvious: security and performance. In our case, we have a service that uses Spring framework and Tomcat as the server and we need to upgrade the build JDK version. Initially it was thought of as a trivial upgrade that primarily involves dealing with dependency resolutions, however, during testing, we immediately faced the problem of potentially shutting down requests from hundreds of customers: the original clients’ requests cannot be accepted by the service because TLS protocols used by legacy clients are not supported by the new JDK version. Updating clients in a short period of time is not feasible as we have no control on customers’ engineering. With the deadline approaching we need to have a solution and the seemingly most straightforward approach was to setup a new set of infrastructure that hosts the service built by the lower version JDK to maintain such backward compatibility. This approach, nevertheless, has the following issues:

  1. Redundant Infrastructure: Minimally a new set of hosts, load balancer and deployment pipeline is required. Although nowadays, Code-As-Infrastructure is widely used, there are still many bootstrap manual steps involved. Additional fleet management and infrastructure cost can be headache as well.
  2. Non-synchronized Code Base: As the software dependencies of two deployment pipelines are different, the two code bases deployed are different. This puts more effort for developers to maintain the system. It might also create the symptom where one specific service can have very specific issue. This non-deterministic nature puts more pressure on developers.
  3. Complex Dependency Management: This involves management of both software dependencies such as version resolution of libraries as well as service dependencies such as upstream services and storage systems. For example, storage systems (e.g. database and blog storage) must be shared so that the service response can maintain its integrity regardless version of client being used, meaning that the permission between compute and storage systems must be redundantly created. More generally speaking, all integrated components need to have redundant infrastructure configurations such as authentication/authorization. This increases burden for both debugging and operational maintenance.

Solution

Fortunately, after carefully looking at JVM documentation, we found that JVM provides “backdoor” that allows legacy TLS protocols to be turned on with developer’s discretion so we tuned JVM with the following changes:

  1. Include a new file that enables legacy TLS protocol together with the deployment:
     jdk.tls.disabledAlgorithms=SSLv3, RC4, DES, MD5withRSA, \
     DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL, \
     include jdk.disabled.namedCurves \
    
  2. Add the following JVM arguments for service startup:
     java.security.properties -D {enableTLSFilePath}
    

And the entire architecture can be summarized as:

JVM-TLS