Study Notes
Free high-yield revision notes for Java Platform, JVM Architecture, JDK and Primitive Types
Java Platform, JVM Architecture, JDK and Primitive Types
Every Java program goes through the same journey: you type source code into a .java file, the JDK's compiler turns it into bytecode stored in .class files, and a Java Virtual Machine (JVM) loads and runs that bytecode. Knowing each step explains everyday messages such as "cannot find symbol" or "integer number too large".
This chapter introduces Java and its features, the JVM architecture, the JDK tools, the structure of a Java class and the eight primitive data types that every other topic builds on.
1. Introduction to Java
Java is a general-purpose, class-based, object-oriented programming language. It was created at Sun Microsystems by a team led by James Gosling and released publicly in 1995. Oracle acquired Sun in 2010 and today Java is developed in the open through the OpenJDK project.
Since Java 10 (2018) a new version appears every six months. Some versions are designated long-term support (LTS) releases, which companies keep using for years: Java 8 (2014), Java 11 (2018), Java 17 (2021) and Java 21 (2023). This course uses the behaviour of Java 8, 11 and 17; where a feature appeared in a particular version it is marked.
This course uses Java SE (Standard Edition), the core language and libraries on which the enterprise (Jakarta EE) and embedded (Java ME) platforms are built.
The central idea of Java is that a program is compiled once into bytecode, an instruction set for an imaginary computer (the JVM), instead of into machine code for one particular processor. Any machine that has a JVM can then run that bytecode.
Hello.java --javac (compile)--> Hello.class (bytecode) --java (JVM)--> program runsHere is the smallest useful program. Save it as Hello.java:
public class Hello {
public static void main(String[] args) {
int a = 2, b = 3;
System.out.println("Sum = " + (a + b));
}
}$ javac Hello.java (creates Hello.class)
$ java Hello (runs the class; note: no .class extension)
Sum = 52. Features of Java
The features below are often called the Java "buzzwords". Each one has a concrete technical reason.
| Feature | What it means in practice |
|---|---|
| Simple and familiar | C/C++-like syntax, but no pointers, no operator overloading, no manual free/delete |
| Object-oriented | Programs are built from classes and objects; all code lives inside classes |
| Platform independent and portable | The same .class files run on any operating system that has a compatible JVM ("write once, run anywhere") |
| Architecture neutral | Primitive sizes are fixed by the language: an int is 32 bits on every machine |
| Robust | Strong static type checking, no pointer arithmetic, array bounds checks, exception handling and automatic garbage collection |
| Secure | Bytecode is verified before it runs and programs cannot read arbitrary memory addresses |
| Interpreted and high performance | Bytecode is first interpreted, and frequently executed ("hot") code is compiled to native code by the JIT compiler |
| Multithreaded | Threads and synchronization are built into the language and libraries |
| Distributed and dynamic | Rich networking libraries; classes are loaded on demand while the program runs |
Platform independence needs a JVM per platform. The bytecode is the same everywhere, but the JVM itself is a native program, built separately for Windows on x64, Linux on ARM and so on.
Architecture neutral means the mini-lab in section 7 prints the same sizes (int 32 bits, long 64 bits, char 16 bits) on every machine, unlike C or C++ where the size of int can vary.
3. JDK, JRE and JVM
These three terms are related but different:
- JVM (Java Virtual Machine): the engine that loads, verifies and executes bytecode, manages memory and runs the garbage collector. It does not compile
.javafiles. - JRE (Java Runtime Environment): a JVM plus the standard class libraries (
java.lang,java.util, ...). Enough to run programs, not to develop them. - JDK (Java Development Kit): a JRE plus development tools such as the compiler. You install a JDK to write Java.
A compact way to remember it: JDK = JRE + development tools and JRE = JVM + class libraries. From Java 11 Oracle no longer ships a separate JRE download; developers install a JDK, and the jlink tool (Java 9+) can build a trimmed runtime image for deployment.
Using the JDK tools
| Tool | Usage |
|---|---|
javac | Compiler: checks source code and writes .class files |
java | Launcher: starts a JVM and runs a class's main method |
javadoc | Generates HTML API documentation from /** ... */ comments |
jar | Packages class files and resources into a .jar archive |
javap | Disassembler: shows the members and bytecode inside a class file |
jshell | Interactive shell for trying statements (Java 9+) |
jdb | Command-line debugger |
The commands you use daily:
javac Hello.java compile; writes Hello.class next to the source
javac -d out Hello.java compile; writes class files under the out directory
java Hello run class Hello from the current directory
java -cp out Hello run Hello, searching the out directory for classes
java Hello.java Java 11+: compile in memory and run a single-file program
javap -c Hello show the bytecode of Hello.classjavactakes a file name (with.java);javatakes a class name (no.class).- The classpath (
-cp/-classpathoption, or theCLASSPATHenvironment variable) lists the directories and JAR files wherejavaandjavaclook for user classes. It is different fromPATH, which only lets the shell find thejavacandjavaprograms, and fromJAVA_HOME, which tells other tools where the JDK is installed. - Every class in a source file, public or not, produces its own
.classfile.
Running javap -c Hello on the class from section 1 shows real bytecode (abridged):
0: iconst_2 push int 2
1: istore_1 store it in local variable a
...
9: iadd add the two intsThe class file contains such JVM instructions plus metadata (a constant pool, field and method descriptions), not native machine code and not your source text.
Compile-time errors versus runtime errors
javac checks names, types, access rules and whether local variables are assigned before use. A mistake such as using an undeclared variable is a compile-time error ("cannot find symbol") and no class file is produced. A runtime error happens in code that compiled successfully, for example integer division by zero throwing ArithmeticException while the program runs.
4. JVM architecture
A JVM has three main subsystems, plus an interface to native code.
.class files
|
Class loader subsystem load -> link (verify, prepare, resolve) -> initialize
|
Runtime data areas shared: method area, heap
per thread: JVM stack, PC register, native method stack
|
Execution engine interpreter, JIT compiler, garbage collector
|
Native method interface JNI + native libraries4.1 Class loader subsystem
Classes are loaded on demand, the first time the program needs them. Using a class goes through three phases:
- Loading: a class loader finds the binary
.classdata and creates the JVM's internal representation of the class. - Linking:
- Verification: the bytecode verifier checks that the code is well-formed and type-safe (for example, it does not jump outside a method or misuse an
intas a reference). This is a key part of Java's security. - Preparation: memory for static variables is allocated and set to default values (0,
false,null). - Resolution: symbolic references (names of other classes, methods and fields) are replaced by direct runtime references, eagerly or lazily.
- Initialization: static variable initializers and
static { }blocks run, top to bottom, exactly once per class.
There are three built-in class loaders, arranged parent to child:
| Loader | Loads | Java 8 name |
|---|---|---|
| Bootstrap | Core classes such as java.lang.String (part of the JVM itself) | Bootstrap |
| Platform | Other Java SE platform modules | Extension class loader |
| Application (system) | Your classes and libraries on the classpath | Application class loader |
Loaders use parent delegation: a loader first asks its parent to load a class and only searches itself if the parent cannot find it. That is why your own class named java.lang.String can never replace the real one.
public class LoaderDemo {
public static void main(String[] args) {
ClassLoader app = LoaderDemo.class.getClassLoader();
System.out.println(app.getName()); // Java 9+
System.out.println(app.getParent().getName());
System.out.println(String.class.getClassLoader());
}
}app
platform
nullString prints null because the bootstrap loader is implemented inside the JVM and has no Java object.
The order of initialization is visible with a static block:
public class InitDemo {
static int count = 10;
static {
System.out.println("static block: count = " + count);
count = 20;
}
public static void main(String[] args) {
System.out.println("main: count = " + count);
}
}static block: count = 10
main: count = 20The class must be initialized before main (a static method) can run, so the static block prints first.
4.2 Runtime data areas
| Area | Shared or per thread | Holds |
|---|---|---|
| Method area | Shared by all threads | Per-class data: runtime constant pool, field and method information, method bytecode. Textbooks also place static variables here. |
| Heap | Shared by all threads | All objects and arrays created with new; managed by the garbage collector |
| JVM stack | One per thread | One frame per method call: local variables, operand stack and return information |
| PC register | One per thread | Address of the bytecode instruction the thread is currently executing |
| Native method stack | One per thread | Frames for native (non-Java) methods called through JNI |
When a method is called, a new frame is pushed on the calling thread's stack; when it returns, the frame is popped and its local variables disappear. Objects stay on the heap as long as something still refers to them. Running out of stack (for example, recursion with no stopping condition) throws StackOverflowError; running out of heap throws OutOfMemoryError.
public class DeepRecursion {
static int depth = 0;
static void dive() {
depth++;
dive(); // no base case: every call adds a frame
}
public static void main(String[] args) {
try {
dive();
} catch (StackOverflowError e) {
System.out.println("Stack overflow after " + depth + " calls");
}
}
}The number printed depends on the JVM and its stack size setting (our run with default settings printed about 55,000).
Implementation note: before Java 8, HotSpot (the most widely used JVM) stored class metadata in a heap region called PermGen; since Java 8 it uses native memory called Metaspace. The JVM specification describes logical areas, not fixed physical regions.
4.3 Execution engine
- The interpreter reads bytecode instructions one by one and executes them. It starts quickly but is slower for code that runs millions of times.
- The JIT (just-in-time) compiler watches which methods run often and compiles these hot methods into native machine code while the program runs, so later calls run at native speed. This is why Java is described as both interpreted and compiled.
- The garbage collector finds heap objects that can no longer be reached from any live reference and reclaims their memory. Garbage collection is covered in detail in a later topic.
4.4 Native method interface
The Java Native Interface (JNI) lets Java code call functions written in languages such as C, stored in native libraries (.dll, .so).
5. Structure of a Java class
A source file is organized in a fixed order:
- an optional
packagedeclaration (at most one, and it must come first); - any
importdeclarations; - one or more type declarations (classes, interfaces, enums).
Inside a class, members can appear in any order, but a conventional layout is: fields, constructors, then methods.
package com.example; // 1. package declaration (optional)
import java.util.ArrayList; // 2. import declarations
import java.util.List;
/** A student with a list of marks. */ // documentation comment, read by javadoc
public class Student { // 3. class declaration
private static int count; // static (class) variable: one copy
private final String name; // instance variables: one copy per object
private final List<Integer> marks = new ArrayList<>();
public Student(String name) { // constructor: initializes a new object
this.name = name;
count++;
}
public void addMark(int mark) { // instance method
marks.add(mark);
}
public double average() {
int total = 0; // local variable
for (int m : marks) {
total += m;
}
return marks.isEmpty() ? 0 : (double) total / marks.size();
}
public static void main(String[] args) { // entry point
Student s = new Student("Asha");
s.addMark(70);
s.addMark(85);
/* block comment */
System.out.println(s.name + " " + s.average() + " " + count);
}
}$ javac -d out Student.java
$ java -cp out com.example.Student
Asha 77.5 1Because of the package, javac -d out writes the class to out/com/example/Student.class, and the launcher is given the fully qualified class name com.example.Student.
Rules to remember:
- A public top-level class must be saved in a file with exactly the same name, including case:
public class Studentbelongs inStudent.java. Otherwisejavacreports "class Student is public, should be declared in a file named Student.java". A file may contain several top-level classes, but at most one of them can be public. - Comments:
//to end of line,/* ... */for a block, and/** ... */for documentation comments. - Import only lets you use a short name (
ArrayListinstead ofjava.util.ArrayList). It does not copy code like a C#includeand does not load the class. Classes injava.lang(such asString,System,Math) and classes in the same package need no import.
The main method
For Java 8, 11 and 17 the launcher looks for exactly this method:
public static void main(String[] args)| Part | Why it is needed |
|---|---|
public | The launcher, which is outside your class, must be able to call it |
static | It is called without first creating an object of the class |
void | It returns nothing to the launcher (use System.exit(status) to return a status code) |
main | The name the launcher searches for; Java is case-sensitive, so Main is a different method |
String[] args | The command-line arguments that follow the class name |
String... args is also accepted, the parameter can have any name, and the order static public is legal. If no suitable main exists, the program still compiles, but the launcher stops with an error such as "Main method not found in class Hello". (Java 25 adds simpler instance main methods for small programs; this course uses the classic form.)
6. Primitive data types
Java has exactly eight primitive types. A primitive variable stores the value itself. Everything else (String, arrays, your own classes) is a reference type, whose variables hold a reference to an object; reference types are covered in topic 3.
| Type | Size | Range | Default (field) | Example literal |
|---|---|---|---|---|
byte | 8 bits, signed | -128 to 127 | 0 | (byte) 10 |
short | 16 bits, signed | -32,768 to 32,767 | 0 | (short) 300 |
int | 32 bits, signed | -2,147,483,648 to 2,147,483,647 | 0 | 42, 0x2A, 0b101010, 052 |
long | 64 bits, signed | about -9.22 x 10^18 to 9.22 x 10^18 | 0L | 8_000_000_000L |
float | 32 bits, IEEE 754 | about ±3.4 x 10^38, 6-7 significant digits | 0.0f | 3.14F |
double | 64 bits, IEEE 754 | about ±1.8 x 10^308, 15-16 significant digits | 0.0 | 3.14, 1.8e1 |
char | 16 bits, unsigned | 0 to 65,535 ('\u0000' to '\uffff') | '\u0000' | 'A', '\n', '\u0041' |
boolean | not precisely defined | true or false | false | true |
For a signed integer type of b bits the range is -2^(b-1) to 2^(b-1) - 1; for byte, -2^7 to 2^7 - 1 = -128 to 127. Java has no unsigned keyword; char is the only unsigned primitive.
6.1 Integer types: byte, short, int, long
int is the default choice for whole numbers; use long when values can exceed about 2.1 billion.
Integer literals are of type int unless they end in L. Writing long big = 8000000000; fails with "integer number too large" because 8000000000 does not fit in an int; 8000000000L is a long literal. Prefer uppercase L, since lowercase l looks like the digit 1.
Literals can be written in several bases, and (since Java 7) underscores may separate digits:
public class Literals {
public static void main(String[] args) {
int decimal = 42;
int hex = 0x2A; // base 16
int octal = 052; // leading 0 means base 8
int binary = 0b10_1010; // base 2, Java 7+
long big = 8_000_000_000L;
System.out.println(decimal + " " + hex + " " + octal + " " + binary);
System.out.println(big);
}
}42 42 42 42
8000000000Overflow wraps around. Integer arithmetic uses fixed-width two's-complement, so going past the maximum silently wraps to the minimum; no exception is thrown:
public class Overflow {
public static void main(String[] args) {
int max = Integer.MAX_VALUE;
System.out.println(max);
System.out.println(max + 1); // wraps to the minimum
long safe = max + 1L; // long arithmetic: no overflow
System.out.println(safe);
}
}2147483647
-2147483648
2147483648Math.addExact(max, 1) throws ArithmeticException instead, when overflow must be detected.
6.2 Floating-point types: float and double
Floating-point literals such as 3.14 are double by default. A float needs an F suffix: float rate = 3.14F; (without it, the assignment fails to compile because double to float loses precision). double is the normal choice.
Binary floating point cannot represent most decimal fractions exactly, so results can carry small errors; avoid float/double for exact money values.
public class FloatingPoint {
public static void main(String[] args) {
System.out.println(0.1 + 0.2);
System.out.println(10.0 / 0); // floating-point division by zero
System.out.println(0.0 / 0);
float f = 1.1F;
System.out.println(f * 2);
}
}0.30000000000000004
Infinity
NaN
2.2Floating-point division by zero gives Infinity or NaN (not a number), whereas integer division by zero throws ArithmeticException.
6.3 char
A char is an unsigned 16-bit UTF-16 code unit. Java uses Unicode rather than ASCII, so a char can hold letters from Hindi, Tamil or Chinese as well as English. Characters can be written as literals ('A'), escape sequences ('\n', '\t', '\'', '\\') or Unicode escapes ('\u0041' is 'A'). Characters outside the Basic Multilingual Plane, such as many emoji, need two char values (a surrogate pair), so one char is not always a complete character.
Because a char is numeric underneath, it takes part in arithmetic. In an expression, a char is promoted to int; but ++ on a char variable keeps the type char:
public class CharDemo {
public static void main(String[] args) {
char c = 'A';
System.out.println(c + 1); // char promoted to int: 65 + 1
c++; // still a char
System.out.println(c);
char fromCode = 97; // constant 97 fits in char
System.out.println(fromCode);
System.out.println((int) 'z');
}
}66
B
a
1226.4 boolean
A boolean has only two values, true and false. Unlike C and C++, Java does not treat integers as booleans: if (1) does not compile, and you cannot cast between boolean and any numeric type. The language does not specify a size for boolean, and Java has no sizeof operator.
6.5 Default values and local variables
Fields (static and instance variables) and array elements receive the default values listed in the table automatically. Local variables (declared inside a method) get no default; the compiler rejects any read of a local that might not have been assigned. This is called definite assignment, and it is explained with examples in topic 2.
public class Defaults {
static int count;
static double rate;
static boolean flag;
static char letter;
static String text; // a reference type: default null
public static void main(String[] args) {
System.out.println(count + " " + rate + " " + flag);
System.out.println((int) letter + " " + text);
}
}0 0.0 false
0 nullThe char default '\u0000' is an invisible character, so the program casts it to int to show 0.
7. Mini-lab: sizes and ranges of every primitive type
Each primitive has a matching wrapper class in java.lang (Byte, Short, Integer, Long, Float, Double, Character, Boolean) whose constants describe the type. Wrapper classes are studied in topic 9; here they simply report the limits.
public class PrimitiveRanges {
public static void main(String[] args) {
System.out.println("byte " + Byte.SIZE + " bits " + Byte.MIN_VALUE + " to " + Byte.MAX_VALUE);
System.out.println("short " + Short.SIZE + " bits " + Short.MIN_VALUE + " to " + Short.MAX_VALUE);
System.out.println("int " + Integer.SIZE + " bits " + Integer.MIN_VALUE + " to " + Integer.MAX_VALUE);
System.out.println("long " + Long.SIZE + " bits " + Long.MIN_VALUE + " to " + Long.MAX_VALUE);
System.out.println("float " + Float.SIZE + " bits max " + Float.MAX_VALUE);
System.out.println("double " + Double.SIZE + " bits max " + Double.MAX_VALUE);
System.out.println("char " + Character.SIZE + " bits " + (int) Character.MIN_VALUE + " to " + (int) Character.MAX_VALUE);
System.out.println("smallest positive double " + Double.MIN_VALUE);
}
}byte 8 bits -128 to 127
short 16 bits -32768 to 32767
int 32 bits -2147483648 to 2147483647
long 64 bits -9223372036854775808 to 9223372036854775807
float 32 bits max 3.4028235E38
double 64 bits max 1.7976931348623157E308
char 16 bits 0 to 65535
smallest positive double 4.9E-324Note the trap in the last line: Double.MIN_VALUE is the smallest positive double, not the most negative one (that is -Double.MAX_VALUE). Extend the lab: try assigning 128 to a byte and read the compiler message.
8. Common mistakes and misconceptions
| Mistake or belief | What is actually true |
|---|---|
"The JVM compiles my .java file" | javac (a JDK tool) compiles source; the JVM runs bytecode, and its JIT compiles hot bytecode to native code at runtime |
| "Java is platform independent, so the JVM is too" | Bytecode is portable; each platform needs its own native JVM |
Running java Hello.class | The launcher takes a class name: java Hello |
"import loads or copies the class" | It only allows a short name at compile time |
| "Overflow throws an exception" | Integer overflow wraps silently; floating-point overflow gives Infinity |
"char is an 8-bit ASCII byte" | char is a 16-bit unsigned UTF-16 code unit |
Public class Hello in Test.java | Compile error: the file must be named Hello.java |
9. Key points
javaccompiles source into platform-neutral bytecode; a JVM on each platform loads and runs it.- JDK = JRE + development tools; JRE = JVM + class libraries.
- JVM architecture: class loader subsystem (bootstrap, platform, application loaders; load, link, initialize), runtime data areas (shared method area and heap; per-thread stack, PC register, native method stack), execution engine (interpreter, JIT, garbage collector) and JNI.
- Source file order: package, imports, types; a public class lives in a file of the same name;
public static void main(String[] args)is the entry point. - Eight primitives:
byte8,short16,int32,long64,float32,double64,char16 unsigned,boolean. Integer literals areint(useL), floating literals aredouble(useF). - Fields get defaults (0, 0.0,
'\u0000',false,null); local variables do not.