Java Security Manager: Permission Checks and Policy Files
A deep dive into the Java Security Manager, policy file configuration, permission checks, and how to properly sandbox untrusted code in the JVM.
Java Security Manager: Permission Checks and Policy Files
The Java Security Manager is one of those JVM features that sits quietly in the background until you need to run untrusted code in a sandboxed environment. If you have ever needed to execute user-provided scripts, plugins, or third-party libraries that you did not fully trust, the Security Manager was designed precisely for that scenario. It has been deprecated since Java 17 but understanding it remains valuable for anyone maintaining older systems or studying JVM internals.
This covers how the Security Manager works, how to configure it with policy files, and what to watch out for in production.
Introduction
The Java Security Manager is a JVM feature that enforces permission-based access control at runtime, deciding whether code can perform sensitive operations like reading files, opening network sockets, or creating class loaders. It was designed for scenarios where you need to run untrusted code inside the same JVM process — plugins, scripts, mobile code — where OS-level process isolation is not available. The Security Manager checks permissions by consulting policy files that grant specific capabilities to code originating from particular locations or signed by particular entities. Combined with the AccessController class and the doPrivileged mechanism for privilege escalation, it forms the foundation of Java’s sandbox security model that made applets and early mobile code practical.
Though deprecated since Java 17, the Security Manager remains relevant for two reasons. Legacy applications built before the deprecation still depend on it for their security model, and understanding its architecture clarifies why modern alternatives like the Java Module System and containerization exist. Second, studying the Security Manager illuminates JVM internals: ProtectionDomain and how class loaders associate permissions with code, AccessController.doPrivileged and how privilege escalation works across call stacks, and the distinction between runtime permission checks and compile-time encapsulation. These concepts appear throughout Java security and reflective APIs regardless of whether the Security Manager itself is in use.
This post covers how the Security Manager works architecturally, how to configure it with policy files, the permission class hierarchy that controls access to specific resource types, how to implement custom permissions for domain-specific access control, production failure scenarios and their root causes, and the trade-offs between the Security Manager, the Java 9 Module System, and OS-level containerization. By the end, you will understand when sandboxing untrusted code inside the JVM makes sense, what the Security Manager can and cannot protect against, and how to migrate away from it toward more maintainable alternatives.
When to Use the Security Manager
The Security Manager handles cases where you need to isolate and restrict untrusted code. It acts as a gatekeeper deciding whether a piece of code can perform sensitive operations like reading files, opening network connections, or modifying system properties.
Good use cases include:
- Running user-provided plugins or scripts in an embedded interpreter (Groovy, Nashorn, BeanShell)
- Executing mobile code downloaded from the network in an applet-style environment
- Sandboxing third-party libraries that require filesystem or network access control
- Legacy applications that built their security model around the Security Manager
Cases where you should look elsewhere:
- New projects should consider the Java Module System (
--add-opens,--add-exports) introduced in Java 9 instead - Modern microservice architectures typically rely on OS-level containerization (Docker, Kubernetes) for isolation
- When you need fine-grained access control beyond what the Security Manager provides, look to language runtimes like WebAssembly or GraalVM Truffle
When NOT to Use
For most modern applications running in containers, the Security Manager is unnecessary overhead. Docker and Kubernetes provide process isolation at the OS level, which is stronger than what the Security Manager offers. If your microservices run in containers with proper network policies and resource limits, the Security Manager duplicates work the platform already does.
Java 9 and later applications should prefer the Module System for encapsulation. Modules enforce which packages are exported, which internals are accessible, and which modules can reflect on each other at compile time and runtime. This is more robust than the Security Manager’s permission model and adds no runtime overhead for permission checks.
Do not use the Security Manager when you control the deployment and the code is trusted. Running your own code in your own infrastructure? The Security Manager adds CPU overhead for checks that serve no purpose. Enable it only when executing untrusted third-party code you cannot isolate at the process level.
Architecture Overview
Understanding how the Security Manager fits into the JVM requires looking at how permission checks flow through the system. If you are new to JVM internals, it helps to first understand how the JVM is structured and the relationship between JDK, JRE, and JVM.
graph TD
A[Untrusted Code Calls<br/>System.exit] --> B[SecurityManager<br/>checkPermission]
B --> C[AccessController<br/>checkPermission]
C --> D[Policy File Parsing<br/>Policy.getInstance]
D --> E[ProtectionDomain<br/>permissions]
E --> F{Permission<br/>Granted?}
F -->|Yes| G[Operation Completes]
F -->|No| H[SecurityException<br/>Thrown]
B --> I[DoAs Privileged Block<br/>Context Check]
I --> C
The flow starts when untrusted code attempts a privileged operation. The JVM calls the Security Manager, which delegates to AccessController. The AccessController consults the loaded policy files to determine what permissions the calling code possesses. If the required permission exists, the operation proceeds. If not, a SecurityException halts execution.
Policy File Configuration
Policy files define what permissions are granted to code from different sources. Each policy file is built from grants that specify code source and associated permissions.
// Example policy file: myapp.policy
// Grant permissions to code from a specific jar
grant codeBase "file:/opt/myapp/lib/plugin.jar" {
permission java.io.FilePermission "/tmp/plugin-data", "read,write";
permission java.net.SocketPermission "localhost:8080", "connect";
permission java.lang.RuntimePermission "createClassLoader";
};
// Grant permissions to all code in a directory
grant codeBase "file:/opt/myapp/-" {
permission java.util.PropertyPermission "user.dir", "read";
permission java.io.FilePermission "/var/myapp/-", "read,write";
};
// Principal-based grant (for authenticated users)
grant Principal "com.sun.security.auth.UnixPrincipal" "pronit" {
permission java.io.FilePermission "/home/pronit/-", "read,write";
permission java.net.SocketPermission "api.example.com:443", "connect";
};
The codeBase attribute specifies where the code originates. The special token - at the end of a file path means “all files in this directory and subdirectories.” Permissions are additive across multiple grant entries for the same code source.
Setting the Policy File at Runtime
You can specify the policy file when starting the JVM:
# Single policy file
java -Djava.security.policy=/path/to/myapp.policy MyApplication
# Multiple policy files (use == to replace default, += to append)
java -Djava.security.policy==/path/to/custom.policy MyApplication
# Debug policy loading
java -Djava.security.policy=/path/to/policy -Djava.security.debug=access:failure MyApplication
You can also programmatically reload policy files without restarting the JVM:
import java.security.Policy;
public class ReloadPolicyExample {
public static void reloadPolicy() {
Policy policy = Policy.getInstance("JavaPolicy");
// In some implementations, you can refresh the policy
policy.refresh();
System.out.println("Policy reloaded successfully");
}
}
Permission Classes
Java defines a rich hierarchy of permission classes, each controlling access to a specific resource type.
import java.io.FilePermission;
import java.net.SocketPermission;
import java.security.PropertyPermission;
import java.lang.RuntimePermission;
// File system access
FilePermission filePerm = new FilePermission("/path/to/file", "read");
FilePermission dirPerm = new FilePermission("/var/data/-", "read,write,delete");
// Network access
SocketPermission socketPerm = new SocketPermission("example.com:80", "connect");
SocketPermission acceptPerm = new SocketPermission("localhost:9000", "accept,listen");
// System properties
PropertyPermission propPerm = new PropertyPermission("user.name", "read");
PropertyPermission envPerm = new PropertyPermission("PATH", "read");
// Runtime behavior
RuntimePermission runtimePerm = new RuntimePermission("createClassLoader");
RuntimePermission exitPerm = new RuntimePermission("exitVM");
The permission string syntax varies by permission type. For FilePermission, actions like read, write, delete, and execute are standard. For SocketPermission, connect, accept, listen, and resolve define the allowed operations.
Implementing Custom Permissions
Sometimes built-in permissions are not enough. You can create custom permission classes for domain-specific access control.
package com.myapp.security;
import java.security.BasicPermission;
public final class CustomResourcePermission extends BasicPermission {
private final String action;
public CustomResourcePermission(String name, String action) {
super(name);
this.action = action;
}
public String getAction() {
return action;
}
@Override
public boolean implies(Permission p) {
if (!(p instanceof CustomResourcePermission)) {
return false;
}
CustomResourcePermission other = (CustomResourcePermission) p;
return this.getName().equals(other.getName())
&& (this.action == null || this.action.equals(other.action));
}
}
Custom permissions extend BasicPermission for simple name-based permissions or Permission directly for complex logic involving multiple attributes.
Production Failure Scenarios
Understanding how the Security Manager fails in production helps you diagnose issues quickly.
| Failure Scenario | Symptoms | Root Cause | Solution |
|---|---|---|---|
| Permission denied for legitimate operation | SecurityException in logs, operation fails silently | Policy file missing grant for required permission | Audit policy file, add missing permission |
| Policy file not loaded | All privileged operations fail with SecurityException | Incorrect path or -Djava.security.policy not set | Verify path, check file permissions |
| Caching issues after policy update | Old permissions persist after edit | JVM caches policy, no refresh mechanism | Restart JVM or call Policy.refresh() |
| ClassLoader boundary confusion | Plugin code cannot access its own resources | ProtectionDomain mismatch between classloaders | Align codeBase grants with ClassLoader hierarchy |
| DoAs context lost | Privileged operations fail after Subject.doAs | Subject not propagated correctly | Check Subject-populated SecurityContext |
Trade-off Analysis
Before committing to the Security Manager, weigh these practical considerations.
| Aspect | Security Manager | Module System (Java 9+) | Container Isolation |
|---|---|---|---|
| Granularity | Fine-grained (file, socket, property) | Coarse (module level exports) | Very coarse (namespace level) |
| Runtime overhead | Medium (permission checks on every operation) | Low (compile-time enforcement) | Minimal |
| Configuration | Text-based policy files | JVM arguments, module-info.java | Dockerfile, Kubernetes manifests |
| Flexibility | Dynamic permission grants at runtime | Static module boundaries | Runtime pod scaling |
| Maintenance | Deprecated, limited evolution | Active development | Active development |
| Use case fit | Plugin sandboxing | Library encapsulation | Microservice isolation |
Implementation Snippets
Here are practical patterns you will encounter when working with the Security Manager.
Checking Permissions Programmatically
public class SecurityCheckExample {
public void performProtectedOperation() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new FilePermission("/critical/data", "read"));
}
// Proceed with operation
readCriticalData();
}
public void checkPropertyAccess(String propertyName) {
try {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new PropertyPermission(propertyName, "read"));
}
String value = System.getProperty(propertyName);
System.out.println(propertyName + " = " + value);
} catch (SecurityException e) {
System.err.println("Access denied to property: " + propertyName);
}
}
}
Using doPrivileged for Elevated Operations
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
public class PrivilegedExample {
public String readSystemProperty(String key) {
return AccessController.doPrivileged((PrivilegedAction<String>) () ->
System.getProperty(key)
);
}
public void writeFilePrivileged() throws Exception {
AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
// This code runs with elevated privileges
// Only use when necessary
System.out.println("Writing with elevated privileges");
return null;
});
}
}
doPrivileged allows code to perform operations with the permissions of a specific Subject, effectively temporarily acquiring elevated access for a limited scope.
Observability Checklist
Here is what to watch when running the Security Manager in production.
- Enable security debugging flags:
-Djava.security.debug=access,failure - Log all SecurityException occurrences with stack traces
- Track permission check latency in hot paths
- Monitor for unexpected permission denials as indicators of misconfiguration
- Audit policy file changes through version control
- Set up alerts for repeated permission failures from the same ProtectionDomain
- Track SecurityManager enable/disable state across application restarts
- Monitor JVM exit codes when
System.exitis blocked
# Enable comprehensive security debugging
java -Djava.security.debug=access,policy,results \
-Djava.security.manager \
-cp myapp.jar com.myapp.Main
# Monitor permission failures
java -Djava.security.debug=failure \
-Djava.security.manager \
-cp myapp.jar com.myapp.Main 2>&1 | grep "access denied"
Security Notes
A few critical security considerations when using the Security Manager.
First, the Security Manager is not a silver bullet. It can only enforce permissions for code that goes through the Security Manager checks. Native code (JNI), the compiler itself, and JVM internals operate outside these controls. Never treat the Security Manager as equivalent to process-level isolation.
Second, policy files themselves must be protected. A policy file that grants excessive permissions defeats the purpose. Store policy files with the same care you would give to passwords, and ensure they are version-controlled so changes are auditable.
Third, the FilePermission actions string uses a flexible but potentially confusing syntax. The action read,write does not include delete. You must explicitly grant delete if you want to allow file deletion.
Fourth, when running multiple plugins from different trust levels, ensure each has its own ProtectionDomain with appropriately scoped permissions. Sharing a ProtectionDomain means sharing all permissions.
Common Pitfalls / Anti-Patterns
These mistakes appear frequently when working with the Security Manager.
Assuming the Security Manager is enabled by default. In modern JVM versions, the Security Manager is disabled unless explicitly activated with -Djava.security.manager. Many developers are surprised to find their sandbox is not actually sandboxed.
Using allPermission in policy grants. Granting java.security.AllPermission completely defeats the purpose of using the Security Manager. Reserve this for testing only.
Forgetting that permissions are not inherited across ClassLoaders. Code from a new ClassLoader gets no permissions by default unless the policy explicitly grants them based on the codeBase.
Not checking for SecurityManager existence. Always call System.getSecurityManager() and handle the null case before invoking permission checks. Running without a Security Manager causes NullPointerException on every check.
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(targetPermission);
}
Mismatched file paths in policy grants. Policy file paths are interpreted differently depending on the operating system. Use canonical paths and test on all target platforms.
Quick Recap Checklist
Use this checklist when deploying the Security Manager in a production environment.
- Verify the Security Manager is actually enabled (
-Djava.security.managerset) - Confirm policy file path is correct and accessible
- Test with
-Djava.security.debug=failurebefore production - Ensure no
grant { permission java.security.AllPermission ... }entries exist - Verify each ClassLoader has appropriate codeBase grants
- Test all plugin scenarios under the restricted Security Manager
- Document all required permissions for each component
- Set up monitoring for SecurityException patterns
- Review policy files for overly broad grants like
file:/// - Plan for the eventual migration to Module System in Java 9+
Interview Questions
When a SecurityException is thrown, the current thread of execution terminates immediately if the exception is not caught. Any operation that triggered the permission check does not complete. The exception propagates up the call stack until it is caught by an exception handler or reaches the top level, which typically results in the thread terminating and an error being logged. In a multi-threaded application, other threads continue unaffected since SecurityException is not typically a fatal condition for the entire process unless the uncaught exception handler treats it as such.
The SecurityManager is the top-level API that applications interact with directly when performing permission checks. The AccessController is a utility class that the SecurityManager delegates to for the actual permission resolution logic. AccessController handles more complex scenarios like privilege escalation through doPrivileged blocks, the doAs mechanism for Subject-based authorization, and the chain of trust that walks through the call stack. In practice, you call SecurityManager.checkPermission() in application code, which internally calls AccessController.checkPermission().
A ProtectionDomain represents a grouping of code with a common set of permissions. Each ClassLoader instance creates ProtectionDomains for the classes it loads, and each ProtectionDomain is associated with a code source (the location the class was loaded from) and a set of permissions granted to that code. When a permission check occurs, the Security Manager looks at the ProtectionDomain of the calling code to determine what permissions it possesses. Two classes from the same JAR file typically share a ProtectionDomain and thus the same permissions, while classes from different sources have different ProtectionDomains. For more on how classes load and link, see [Java Classes and Objects](/blog/technical/java-classes-and-objects/).
The Security Manager was deprecated because it had significant limitations that made it unsuitable for modern security requirements. It was designed in the 1990s for a world of applets and mobile code, but it could not address many attack vectors like side-channel attacks, timing attacks, or speculative execution vulnerabilities. The JDK itself had numerous exceptions and internal APIs that bypassed Security Manager checks, creating inconsistency. The Java Module System introduced in Java 9 provides stronger encapsulation at the module boundary level, and modern deployments rely on OS-level containerization (Docker, Kubernetes namespaces) for isolation. For plugin scenarios, consider running untrusted code in a separate process with IPC rather than in the same JVM.
checkPermission performs a permission check against the entire call stack, verifying that every frame on the stack has the required permission. This is the default behavior and provides defense in depth. doPrivileged temporarily elevates the permissions for a specific block of code, allowing it to perform privileged operations even if intermediate callers on the stack lack those permissions. After the doPrivileged block completes, normal permission checking resumes. The key difference is that doPrivileged is necessary when library code needs to perform privileged operations on behalf of untrusted code higher on the stack that lacks those permissions itself.
Each ProtectionDomain is associated with a code source (the location and signer of the code) and a set of permissions. When a ClassLoader defines a class, it associates that class with a ProtectionDomain based on the code source of the class bytecode. Different ClassLoaders create different ProtectionDomains even for classes from the same location. For plugin security, this means plugins loaded by separate ClassLoaders have separate ProtectionDomains and separate permission sets. A plugin cannot access another plugin's resources unless the policy explicitly grants cross-ClassLoader permissions, which is rarely done.
Permissions are additive across multiple grant entries for the same code source in a policy file. If one grant gives read access to /tmp and another grant gives write access to /tmp, the effective permission is read and write. There is no concept of deny in the Security Manager permission model. If two grants conflict, both are granted. This means policy file authors must be careful not to inadvertently grant more permissions than intended by having multiple overlapping grants.
The Security Manager was deprecated in Java 17 because it could not address modern security requirements. It was designed in the 1990s for applets but cannot prevent side-channel attacks, timing attacks, or speculative execution vulnerabilities. The JDK itself had numerous internal API exceptions that bypassed Security Manager checks, creating inconsistencies. For modern deployments, the Java Module System provides encapsulation at the module level, and containerization (Docker, Kubernetes) provides process isolation at the OS level. For running untrusted code, separate processes with IPC are recommended over sharing a JVM.
checkPermission is the primary entry point for permission checks in the Security Manager. When code calls System.getSecurityManager().checkPermission(permission), it delegates to AccessController.checkPermission which walks the call stack verifying each frame has the required permission. The Subject.doAs pattern associates a Subject (representing a authenticated principal) with a thread. When AccessController.checkPermission runs in a doAs context, it evaluates permissions based on the Subject's principals and the code's ProtectionDomain, enabling principal-based authorization where the identity of the caller matters for permission decisions.
When doPrivileged is called within another doPrivileged block, the permission check only considers the most recent privilege context. The inner doPrivileged effectively hides the call stack above it from permission checking. This nesting can be dangerous if the inner block grants more permissions than expected. Multiple nested doPrivileged blocks are evaluated as a single privilege escalation point. Best practice is to minimize the scope of doPrivileged blocks and ensure the elevated privileges are necessary and minimal.
FilePermission controls access to the file system. The valid actions are: read, write, delete, and execute. The permission string uses a path and actions format: "/path/to/file" or "/path/to/directory/-" where the hyphen means all files and subdirectories. Important: the "read" action does not imply "write", "write" does not imply "delete", and "execute" does not imply "read". You must explicitly grant each action needed. For example, "read,write" does not include "delete" or "execute".
The Security Manager permission checks apply to the calling thread individually, not to the entire ThreadGroup. Each thread has its own call stack and its own permission evaluation. However, when a thread creates a new Thread or ThreadGroup, the SecurityManager.checkAccess(ThreadGroup) method is called to verify the calling code has permission to modify the ThreadGroup. This is one of the few cases where ThreadGroup interacts with the Security Manager. Thread-level security checks do not automatically apply to sibling threads.
Granting no permissions means the code cannot perform any privileged operations at all. Any operation that requires a permission check will fail with SecurityException. AllPermission grants every possible permission, effectively disabling the security sandbox entirely. The code can read any file, connect to any network, create ClassLoaders, exit the JVM, and modify security properties. AllPermission is only appropriate for trusted system code during development. In production, using AllPermission completely defeats the purpose of the Security Manager.
Native methods are not checked by the Security Manager because they execute outside the JVM's control. Once a native method is invoked, it has full access to system resources independent of the Security Manager. This is why calling System.exit() from native code bypasses the SecurityManager.checkExit() call. The security boundary between Java and native code is a known limitation. Any code that can invoke native methods (through JNI or JNA) effectively has full system access regardless of the Security Manager configuration.
The java.security.manager system property controls whether the Security Manager is enabled at JVM startup. Setting -Djava.security.manager enables the default Security Manager implementation. Setting -Djava.security.manager=allow provides more granular control over which code can disable the Security Manager. Without this property set, System.getSecurityManager() returns null and all permission checks become no-ops. Many applications that intended to use the Security Manager were found not to be using it because the property was not set.
First, enable security debugging with -Djava.security.debug=failure to see exactly which permission was denied and why. Check the policy file loaded in production versus development—they may differ. Verify the codeBase paths in the production policy file point to the correct JAR locations. Check if the ClassLoader hierarchy differs in production, causing different ProtectionDomains to be used. Verify the JDK version is the same, as some permissions behave differently across versions. Finally, check if an agent or instrumentation is modifying class bytecode in production but not in development.
The RuntimePermission("createClassLoader") controls whether code can create a new ClassLoader instance. Plugins that need to dynamically load classes at runtime require this permission. However, most plugins do not need this permission if they only use the service provider pattern (ServiceLoader) or receive pre-loaded classes from the host application. Creating ClassLoaders is powerful because it allows code to load classes from arbitrary locations with their own ProtectionDomain, effectively creating a new security domain within the JVM. This should be granted only to plugins that genuinely need runtime class loading capability.
The codeBase value in a policy grant supports file URLs. A trailing slash on a directory means that directory and all subdirectories. For example, "file:/opt/myapp/lib/" matches any file under /opt/myapp/lib/. A file URL without a trailing slash matches only that exact file. Wildcards in the traditional sense (like *) are not supported in codeBase URLs. The hyphen shorthand like "/var/myapp/-" is equivalent to "/var/myapp/**" meaning recursively all files under /var/myapp/. HTTPS code sources can use wildcards in the hostname portion like "https://*.example.com/-".
The exitVM permission allows code to terminate the entire JVM process with System.exit(). For plugins, this is extremely dangerous because a single plugin can shut down the entire application. Plugins should never be granted exitVM permission. If a plugin needs to signal an error condition, it should throw an exception rather than exit the JVM. The host application should catch plugin exceptions and handle them gracefully without terminating the process. Grant exitVM only to trusted infrastructure code that needs to shut down the JVM as part of normal operation.
Both mechanisms can coexist in Java 9+. The module system controls compile-time and reflection-time access through exports and opens directives. The Security Manager controls runtime permission checks for operations like file access and network connections. They operate at different layers. When a module attempts to access an unexported package, the module system throws IllegalAccessError before any Security Manager check occurs. When code accesses an exported but protected resource, the Security Manager performs its permission check. For modules that need runtime reflection access, the opens directive bridges the two systems by allowing deep reflection while still allowing Security Manager permission checks to operate.
Further Reading
- Java Security Manager Documentation - Official Oracle documentation covering Security Manager configuration, policy file syntax, and permission classes.
- JEP 411: Deprecate the Security Manager for Removal - The official JEP detailing why the Security Manager was deprecated and what alternatives exist.
- Understanding Policy File Permissions - Comprehensive guide to writing policy files with codeBase, principal grants, and permission chaining.
- AccessController.doPrivileged Patterns - Deep dive into privilege escalation and how to use doPrivileged correctly without creating security holes.
- Migrating from Security Manager to Module System - Oracle’s guide on transitioning to Java 9+ module system and container isolation.
Conclusion
The Java Security Manager provides a sandbox mechanism for isolating untrusted code within the JVM. Though deprecated since Java 17, understanding its architecture and policy-based permission system remains valuable for legacy system maintenance and JVM internals education. The key to using the Security Manager effectively lies in properly scoping permissions, avoiding allPermission grants, and understanding that it supplements rather than replaces OS-level containerization.
For new projects, rely on Java’s Module System (--add-opens, --add-exports) for encapsulation and Docker/Kubernetes for process isolation. If you must run untrusted code in the same JVM, consider alternative isolation mechanisms like separate processes with IPC rather than the Security Manager. The deprecation reflects a broader shift toward defense-in-depth at multiple layers.
Category
Related Posts
JVM Bytecode Verification: Type Checking and Stack Map Frames
A technical deep dive into the JVM bytecode verifier, covering type checking, stack map frames, the four verification stages, and what happens when verification fails.
CDS and AppCDS: Class Data Sharing for Faster JVM Startup
A guide to Class Data Sharing in the JVM, covering how CDS and AppCDS work, how to create shared archives, and how they reduce startup time and memory footprint.
Class Loader Subsystem: Loading, Linking, and Initialization
Deep dive into the JVM Class Loader subsystem covering loading, linking, initialization phases and the ClassLoader hierarchy with parent delegation model.