Java Programming

JVM Architecture, Class Metadata and Reflection

PGCP-BDA

JVM runtime data areas

The JVM uses shared heap and method metadata plus per-thread stacks, program counters and native stacks to execute loaded classes.

class loader delegation

A class loader normally asks its parent to load a class first, protecting core types and avoiding duplicate definitions before attempting its own source.

bytecode verification

Bytecode verification checks class-file structure, operand-stack use, type safety and access rules before code is executed.

JIT compilation

A just-in-time compiler converts frequently executed bytecode into optimized native machine code using runtime profiling information.

garbage collector

The JVM service that finds unreachable heap objects and reclaims their storage without explicit deallocation by the program.

Class object

A Class object is the JVM’s runtime representation of a loaded primitive, array, interface, enum, record or class type.

reflection

Reflection inspects and, when permitted, dynamically uses types, members, annotations and constructors at runtime.

runtime metadata

Runtime metadata includes type names, hierarchy, modifiers, declared members, generic signatures and retained annotations available through reflection.

annotations

Annotations attach structured metadata to program elements and are available at runtime only when declared with an appropriate retention policy.

dynamic invocation

Reflective invocation resolves a member at runtime and wraps target exceptions, sacrificing some compile-time safety and optimization.

Runtime data areas

The JVM specification describes logical runtime areas:

  • each thread has a program counter and JVM stack;
  • each method invocation creates a frame with local-variable slots, an operand stack and execution data;
  • the heap stores class instances and arrays managed by garbage collection;
  • the method area concept stores per-class structures such as runtime constants, field information and method code;
  • native method support has related runtime structures.

These are logical roles, not a promise that every implementation maps each one to a simple fixed physical region. JIT escape analysis and other optimizations may change physical allocation while preserving observable language behavior.

Compilation and class files

Hello.java --javac--> Hello.class --JVM--> program execution

javac performs lexical, syntactic, type, access and definite-assignment checks before emitting valid class files. A class file contains bytecode, symbolic references, method information and metadata rather than ordinary source or one platform's final machine code.

javac -d out src/com/example/Hello.java
java -cp out com.example.Hello

The -d option places output in a directory hierarchy matching packages. The launcher receives a binary class name, not a source path. The classpath tells classpath-based tools where to locate application classes and libraries.

A compile-time error such as an unknown variable prevents normal class generation for that compilation unit. A runtime exception occurs after valid code has begun executing, such as integer division by zero.

Java lexical tokens

Before parsing declarations and statements, the compiler divides source text into tokens. Keywords such as class, if and return have reserved language meanings. Identifiers name types, methods, variables, packages and other program elements. Literals write fixed values such as 42, 3.5, 'A', true and "text". Operators form expressions, while separators such as parentheses, braces, commas, semicolons and dots organize program structure.

Identifiers are case-sensitive and cannot be a keyword or the literals true, false and null. They may contain permitted Unicode letters, digits after the first character, currency symbols and connector characters, although conventional Java names favor readable letters and digits. Whitespace and comments separate tokens but ordinarily do not become executable instructions. A lexical error, malformed literal or missing separator is diagnosed before type checking can succeed.

Annotations, enums and runtime discovery

Reflection sees an annotation only if it has RUNTIME retention:

if (handlerClass.isAnnotationPresent(Command.class)) {
    Command command = handlerClass.getAnnotation(Command.class);
    registry.put(command.value(), handlerClass);
}

Frameworks use this pattern for dependency injection, persistence mapping, validation and test discovery. Metadata alone performs nothing; framework code must scan, interpret and act.

Enums expose a fixed set of named instances. Reflective code can use Class.isEnum() and getEnumConstants(), but ordinary enum APIs are clearer when the type is statically known.

Loading, linking and initialization

Class use involves related but distinct steps:

  1. Loading: a class loader obtains class binary data and creates the runtime representation.
  2. Verification: the JVM checks structural and bytecode safety constraints.
  3. Preparation: storage for static fields is prepared with initial default values.
  4. Resolution: symbolic references are translated to runtime references, eagerly or lazily as permitted.
  5. Initialization: static field initializers and static initialization blocks execute in textual order.

Merely declaring Widget widget; does not construct a Widget. Loading a class is not the same as creating an instance and preparation defaults are not the same as explicit static initialization.

Values and storage

Java has eight primitive types. Integer types have specified widths; char is an unsigned 16-bit UTF-16 code unit, not a complete representation of every Unicode character. Boolean is a logical type and is not an integer in Java expressions. Reference variables hold references to objects or null. Arrays and strings are reference types. Fields receive default values, but local variables must be definitely assigned before use.

public class Total {
    public static void main(String[] args) {
        int count = 4;
        double average = 15.0 / count;
        System.out.println(average); // 3.75
    }
}

Each invocation has a stack frame containing execution information and local variables; objects are managed by the runtime. The JVM specification defines logical runtime areas and implementations may optimize physical allocation. Garbage collection reclaims unreachable storage; it is not a substitute for closing external resources. Avoid assuming that a reference has a predictable address or that every JVM has identical internal memory organization.

Reflection foundations

Reflection begins with a Class<?> object:

Class<String> a = String.class;
Class<?> b = "text".getClass();
Class<?> c = Class.forName("java.lang.String");

Class literals do not need a string lookup. Class.forName can fail with ClassNotFoundException and may initialize the class.

Reflection can inspect constructors, methods, fields, modifiers, interfaces, superclass information, generic metadata and runtime-retained annotations. Methods named getMethods expose public methods including inherited ones, while getDeclaredMethods exposes methods declared directly in the class, regardless of access, but excludes inherited declarations.

Reflective construction and invocation

Constructor<Service> constructor =
        Service.class.getDeclaredConstructor(String.class);
Service service = constructor.newInstance("production");

Method method = Service.class.getDeclaredMethod("start");
method.invoke(service);

Lookup and invocation are separate stages. Invocation wraps an exception thrown by the target method in InvocationTargetException; inspect its cause for the underlying failure.

Access checks, module boundaries, security policies and encapsulation still apply. Deep reflective access can fail even if setAccessible(true) is requested. Reflection also weakens refactoring safety and shifts errors to runtime, so ordinary typed calls are preferable when the type is known.

Garbage collection and resources

An object becomes eligible for garbage collection when no live path from runtime roots can reach it. Eligibility does not promise immediate reclamation or a predictable order. Assigning null is rarely a required manual memory-management step; object lifetime is usually controlled by scope and references.

Garbage collection reclaims managed memory. It does not reliably close files, database connections, sockets or locks. Those external resources require explicit structured cleanup, commonly try-with-resources.

Complete execution trace

package demo;

public class Average {
    private static final int SUBJECTS = 4;

public static void main(String[] args) {
        int total = args.length == 0 ? 60 : Integer.parseInt(args[0]);
        double average = (double) total / SUBJECTS;
        System.out.printf("Average: %.2f%n", average);
    }
}
  1. javac resolves names and types and emits demo/Average.class.
  2. The launcher locates demo.Average through its runtime path.
  3. The JVM loads, links and initializes the class, assigning SUBJECTS its constant value.
  4. A main frame receives the argument array.
  5. The selected string is parsed or the default is used.
  6. The explicit cast makes the division floating point.
  7. Formatting writes the result; normal completion ends the non-daemon application work.

Invalid numeric input compiles successfully but throws during execution, demonstrating the distinction between compile-time type correctness and runtime data validity.

Program entry point

package com.example;

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, " + (args.length == 0 ? "world" : args[0]));
    }
}

For the conventional Java 17 application launcher entry point:

  • public makes the method accessible to the launcher;
  • static allows invocation without constructing Hello;
  • void means no value is returned;
  • main is the recognized method name;
  • String[] args contains command-line arguments after the class name.

String... args is equivalent for this parameter purpose. Java is case-sensitive: Main and main are different identifiers. Returning an operating-system status is normally done with process facilities rather than changing main's return type.

Continue learning

Related notes

Put this topic into timed practice

Open mock tests when you want full-exam pacing, or keep drilling in practice mode.