What is Platform Independence? #
- What?: Ability of a software to run on different operating systems without changes.
- How Java Achieves It: JVM (Java Virtual Machine) runs the same bytecode on any OS.
- Example: A Java program compiled on Windows runs on Linux or macOS without recompilation.
public class Example { public static void main(String[] args) { System.out.println("Java is platform-independent!"); } }
- C/C++ are NOT platform independent:
- Need separate compilation for each OS.
Platform Independence vs Portability #
π Platform Independence
- What?: Ability of software to run on different operating systems without modification.
- Example:
- Java compiles to bytecode, which runs on any JVM.
- A Java program compiled on Windows runs on Linux or macOS without recompilation.
- Java follows "Write Once, Run Anywhere".
π Portability
- What?: Ability of software to be adapted to different platforms with minor changes.
- Example:
- A C/C++ program can be run on different OSs after seperate compilations for each OS.
- C/C++ are portable but not platform-independent.
π Remember:
- Java is both portable and platform-independent
- C/C++ are portable but not platform-independent
- Assembly Language is neither portable nor platform-independent
Code vs Machine Code vs Bytecode #
π Code (Source Code)
- What?: Human-readable instructions written in a programming language.
- Execution: Needs to be converted to machine code before running on a machine.
- Example (Java Source Code):
public class Hello { public static void main(String[] args) { System.out.println("Hello, World!"); } }
π Bytecode
- What?: Intermediate, platform-independent code executed by a virtual machine (JVM).
- Execution: Runs on the Java Virtual Machine (JVM), which translates it into machine code at runtime.
- Example (Java Bytecode - Generated from
javac Hello.java
):0: ldc "Hello, World!" 2: invokevirtual #2 5: return
π Machine Code
- What?: Low-level binary instructions that a CPU can execute directly.
- Execution: Runs directly on hardware without further translation.
- Example (x86 Machine Code):
10110000 00000101 ; (Move value 5 into register AL) 00000100 00000011 ; (Add 3 to AL)
What are the major events in programming history that led to Java? #
π Key Events
- Writing Machine Code
- Using Assembly Language
- Structured Programming with C Language
- OOPS with C++
- OOPS with Java
π 1: Writing Machine Code
- Machine Code: Consists of binary instructions (
0s
and1s
) that the CPU directly executes- Performs basic operations like moving data, adding numbers, or jumping to another instruction
- (β Advantage) Fastest execution since it runs directly on hardware
- (β Disadvantage) Extremely difficult to write and debug manually
- (β Disadvantage) Different for each CPU architecture
- Example:
10110000 00000101 ; (Move value 5 into register AL) 00000100 00000011 ; (Add 3 to AL)
- Register: A small, high-speed storage location inside the CPU that temporarily holds data while processing instructions
π 2: Using Assembly Language
- Assembly Language: Human-readable version of machine code, using mnemonics like
MOV
,ADD
, andJMP
- Each instruction maps directly to a machine code equivalent
- (β Advantage) Easier than writing raw machine code
- (β Disadvantage) BUT still verbose and difficult to write
- (β Disadvantage) Different for each CPU architecture
- Example 1: Simple Addition:
MOV AX, 5 ; Load 5 into register AX ADD AX, 3 ; Add 3 to AX
- Example 2: Average of 4 numbers:
; Load first number into AX MOV AX, 10 ; AX = 10 ; Load second number into BX MOV BX, 20 ; BX = 20 ADD AX, BX ; AX = AX + BX (10 + 20) ; Load third number into CX MOV CX, 30 ; CX = 30 ADD AX, CX ; AX = AX + CX (30) ; Load fourth number into DX MOV DX, 40 ; DX = 40 ADD AX, DX ; AX = AX + DX (40) ; Divide by 4 to get the average ; for word-sized division ; the quotient is always stored in AX ; the remainder is stored in DX MOV DX, 0 ; Clear DX before division MOV CX, 4 ; Divide by 4 DIV CX ; AX = AX / 4, remainder in DX
π 3: Structured Programming with C Language
- Created by Dennis Ritchie at Bell Labs in the 1970s
- Used structured programming
- (β Advantage) Easier than using assembly language
- (β Advantage) More easier to adapt to different CPU architectures
- (β Disadvantage) Building large apps with hundreds of thousands of lines of code is challenging
- Example:
#include <stdio.h> int main() { int num1 = 10, num2 = 20, num3 = 30, num4 = 40; float average; // Calculate the average average = (num1 + num2 + num3 + num4) / 4.0; // Print the result printf("Average is: %.2f\n", average); return 0; }
π 4: OOPS with C++
- Developed by Bjarne Stroustrup as an extension of C in the 1980s
- Supports both procedural and object-oriented paradigms
- Object Oriented Features: classes, inheritance, and polymorphism
- Example:
#include <iostream> class AverageCalculator { private: int num1, num2, num3, num4; public: // Constructor to initialize numbers AverageCalculator(int a, int b, int c, int d) { num1 = a; num2 = b; num3 = c; num4 = d; } // Function to calculate average float calculateAverage() { return (num1 + num2 + num3 + num4) / 4.0; } // Function to print the average void printAverage() { std::cout << calculateAverage() << std::endl; } // Destructor (optional for debugging) ~AverageCalculator() { std::cout << "Object deleted." << std::endl; } }; int main() { // Creating an object dynamically AverageCalculator* avg = new AverageCalculator(10, 20, 30, 40); // Printing the average avg->printAverage(); // Deallocating memory to prevent memory leaks delete avg; return 0; }
π 5: Java
- Developed by Sun Microsystems in the 1990s
- Introduced platform independence with the JVM (Java Virtual Machine)
- Provides automatic memory management (garbage collection)
- Commonly used for web applications, enterprise software, and Android development
- Example:
class AverageCalculator { private int num1, num2, num3, num4; // Constructor to initialize numbers public AverageCalculator(int a, int b, int c, int d) { this.num1 = a; this.num2 = b; this.num3 = c; this.num4 = d; } // Method to calculate average public double calculateAverage() { return (num1 + num2 + num3 + num4) / 4.0; } // Method to print the average public void printAverage() { System.out.println(calculateAverage()); } } public class Main { public static void main(String[] args) { // Creating an object dynamically AverageCalculator avg = new AverageCalculator(10, 20, 30, 40); // Printing the average avg.printAverage(); //Do NOT worry about managing memory! Automatic GC. } }
JDK vs JVM vs JRE #
π JDK (Java Development Kit)
- What?: A full Java development package that includes everything needed to write, compile, and run Java programs.
- Includes:
- JRE (Java Runtime Environment) β Needed to run Java applications.
- JVM (Java Virtual Machine) β Executes Java bytecode.
- Development Tools β Compiler (
javac
), debugger, and other utilities.
π JRE (Java Runtime Environment)
- What?: A package that provides everything needed to run Java applications but does not include development tools.
- Includes:
- JVM (Java Virtual Machine) β Converts bytecode into machine code.
- Core Java Libraries β Essential classes (
java.lang
,java.util
, etc.).
π JVM (Java Virtual Machine)
- What?: A virtual machine that interprets or compiles Java bytecode into machine code for execution.
- Includes:
- Class Loader β Loads
.class
files into memory. - Bytecode Interpreter β Converts bytecode into native machine code.
- JIT (Just in Time) Compiler β Optimizes performance by compiling frequently used bytecode into native machine code.
- Class Loader β Loads
π Comparison Table: JDK vs JRE vs JVM
Feature | JDK (Development Kit) | JRE (Runtime Environment) | JVM (Virtual Machine) |
---|---|---|---|
Purpose | Develop & run Java applications | Run Java applications | Execute Java bytecode |
Includes JRE? | β Yes | β Yes | β No |
Includes JVM? | β Yes | β Yes | β Itself |
Includes Compiler (javac )? |
β Yes | β No | β No |
Best For | Java developers | End users running Java apps | Running bytecode on any OS |
Static vs Dynamic Programming Languages #
π What?
- How variables are checked in a language.
- Static: Checked at compile-time.
// Static Language (Java) int number = "Hello"; // β Error at compile-time
- Dynamic: Checked at runtime.
# Dynamic Language (Python) number = "Hello" # β No error at assignment number = 10 # β Type can change
- Static: Checked at compile-time.
π Comparison
Feature | Static Languages (Java, C) | Dynamic Languages (Python, JavaScript) |
---|---|---|
Type Checking | Compile-time β | Runtime β |
Performance | Faster π | Slower π’ |
Flexibility For Programmers | Less flexible β | More flexible β |
Error Detection | Early (Compile-time) β | Late (Runtime) β |
Code Safety | More strict β | Less strict β |
π When? and How?
- When to use Static? Large, performance-critical apps.
- When to use Dynamic? Quick prototyping, scripting.
- How to choose?
- Need speed and safety? β Static
- Need flexibility? β Dynamic
How JVM is Reused by Other Programming Languages #
- The JVM is NOT just for Java!
- Many languages compile to JVM bytecode:
- Allows them to benefit from Javaβs ecosystem, portability, and performance optimizations
- Examples:
- Kotlin : Designed as a better Java with more concise syntax.
- Example:
fun main() { val name = "Ranga" println("Hello, $name!") }
- Example:
- Scala: Scala blends object-oriented and functional programming, while Java is primarily object-oriented (functional programming features were added later).
- Example:
object HelloWorld { def main(args: Array[String]): Unit = { println("Hello from Scala!") } }
- Example:
- Groovy: Dynamic scripting language with Java-like syntax.
- Example:
println "Hello from Groovy!"
- Example:
- Others: Jython (Python on JVM), Clojure, ..
- Kotlin : Designed as a better Java with more concise syntax.
Scala, Kotlin and Groovy code can be compiled into JVM bytecode and executed by the JVM.
Advantages of Reusing JVM #
Feature | Benefit for Other Languages |
---|---|
Platform Independence | Runs on any system with a JVM |
Memory Management | Automatic garbage collection |
Performance | JIT (Just-In-Time) Compilation speeds up execution |
Java Interoperability | Can use existing Java libraries |
What is CLASSPATH? #
- What?: CLASSPATH tells the JVM where to find .class files
- Why?: Helps Java locate user-defined classes and external dependencies.
- Default Behavior: If not set, Java looks in the current directory (
.
) and the standard Java libraries. - Setting CLASSPATH:
# Command Line java -cp mylibrary.jar MyClass #Windows set CLASSPATH=C:\myapp\lib; #Linux,Mac export CLASSPATH=/home/user/myapp/lib
What is the role for a classloader in Java? #
- What?: Finds
.class
files and loads them as and when they are needed - Why?:
- Efficient Memory Use: Loads classes only when needed, saving memory.
- Security: Prevents unauthorized access by controlling class loading.
- Custom Class Loading: Supports loading classes from databases, networks, or encrypted files.
Types of Java Class Loaders #
π 1: Bootstrap ClassLoader
- Loads: Core Java classes from the
java.base
module. - Example Packages:
java.lang
(e.g.,java.lang.Object
,java.lang.String
)java.util
(e.g.,java.util.List
,java.util.Map
)java.io
(e.g.,java.io.InputStream
,java.io.OutputStream
)java.nio
(e.g.,java.nio.file.Path
)java.math
(e.g.,java.math.BigDecimal
)
- Hierarchy: Root of the class loader hierarchy (has no parent).
π 2: Platform ClassLoader
- Loads: JDK-defined platform classes from specific modules (excluding
java.base
). - Example Packages:
java.sql
(e.g.,java.sql.Driver
,java.sql.Connection
)java.logging
(e.g.,java.util.logging.Logger
)java.xml
(e.g.,javax.xml.parsers.DocumentBuilderFactory
)java.net
(e.g.,java.net.HttpURLConnection
)
- Hierarchy: Child of the Bootstrap ClassLoader.
π 3: System or Application ClassLoader
- Loads: Classes from the applicationβs classpath.
- Location: Defined by the
CLASSPATH
environment variable or-cp
/--class-path
command-line argument. - Example Packages:
- Custom user-defined packages (
com.in28minutes
,org.example
) - Libraries included in the classpath (
springframework.*
,hibernate.*
,jackson.*
)
- Custom user-defined packages (
- Hierarchy: Child of the Platform ClassLoader.
π 4: Custom ClassLoader (User-Defined)
- What?: Used to load classes from custom sources (network, databases, encrypted storage, etc.).
- How?: Extends
ClassLoader
and overridesfindClass()
. - Example:
class MyClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { // Custom logic to load the class return super.findClass(name); } }
- Example Use Case:
- Dynamic class loading from
.class
files stored in a database. - Loading encrypted classes from a secured source.
- Dynamic class loading from
ClassLoader Delegation Model (Parent-Child Hierarchy) #
- Java uses the Parent Delegation Model to maintain class uniqueness and security.
- 1: Application ClassLoader receives a class request and first asks its parent (Platform ClassLoader).
- 2: Platform ClassLoader then asks the Bootstrap ClassLoader to load the class.
- 3: Bootstrap ClassLoader loads core Java classes (
java.lang.String
,java.util.List
). - (REMEMBER) If the class is not found at any level, the request is passed back down, allowing the child ClassLoader to load it.
- (WHY?) This prevents duplicate class loading and ensures that system classes are not overridden.
How does the ClassLoader Delegation Model prevent system classes from getting overridden? #
- If none of the parent class loaders find the class, only then does the Application ClassLoader attempt to load it from the classpath (
.jar
or.class
files). - System classes (
java.lang.String
,java.util.List
, etc.) are always loaded by the Bootstrap ClassLoader. - Even if a user defines
java.lang.String
in their code, it will not be loaded because the Bootstrap ClassLoader has already loaded the real system version. - Example: The JVM does not allow defining classes in
java.lang
to prevent conflicts.
// Illegal: System classes cannot be overridden
package java.lang;
public class String {
public String customMethod() {
return "This should not work!";
}
}
Result:
Error: The package java.lang is system-restricted.
Compilation vs Interpretation #
Compilation
- What?: Translates the entire source code into machine code before execution.
- Execution Speed: Faster, as the program runs directly as machine code.
- Example Languages: C, C++, Java (compiles to bytecode).
- How?: Source Code β Compiler β Executable β Run
Interpretation
- What?: Translates and executes code line by line at runtime.
- Execution Speed: Slower, since it processes each instruction while running.
- Example Languages: Python, JavaScript, Ruby, Java (JVM interprets bytecode).
- How?: Source Code β Interpreter β Execute (line by line)
Is Java Compiled or Interpreted? #
- Java is compiled into bytecode and then interpreted by the JVM
- Compiled: Java source code (
.java
) is compiled into bytecode (.class
) using thejavac
compiler. - Interpreted: The JVM interprets bytecode and executes it line by line.
- Compiled: Java source code (
- Process Flow
- Java Source Code (
.java
) β Compilation (javac
) β Bytecode (.class
) - Bytecode (
.class
) β JVM Interprets & Executes
- Java Source Code (
How does Java achieve Performance Optimization in spite of Interpretation? #
- What? Java needs to balance portability with good performance.
- How? Provide good performance in spite of being interpreted!
- Compile time optimizations: Small optimizations at compile time
- Dead Code Elimination
- Constant Folding
- Runtime Optimizations: Java tries to optimize frequently executed code (hotspots) at runtime:
- 90-10 Rule: 90% of execution time is spent on 10% of code. How about trying to identify hot spots and optimize them?
- Just-In-Time (JIT) Compilation: Identify hot spots (frequently executed code), convert bytecode into native machine code and reuse it for future executions (avoid re-interpretation)
- Adaptive Optimization: Dynamically optimize based on execution patterns (Method Inlining, ...).
- Compile time optimizations: Small optimizations at compile time
What is Just-In-Time (JIT) Compilation? #
- What?: JVM monitors method execution and compiles frequently used code paths into efficient machine code.
- Why?: Improves performance by avoiding repeated interpretation of "hot" spots
- Example:
- Initially,
compute()
is interpreted by the JVM. - As it is called millions of times, the JIT compiler identifies it as a "hot spot".
- The JVM compiles it into native machine code, improving performance.
public class JITExample { public static void main(String[] args) { long start = System.nanoTime(); for (int i = 0; i < 10_000_000; i++) { compute(); } long end = System.nanoTime(); System.out.println("Execution Time: " + (end - start) + " ns"); } static void compute() { int x = 5 * 10; } }
- Initially,
Give a few examples of optimizations performed by Java? #
π 1: Method Inlining
- What?: Replaces a method call with its actual body to avoid function call overhead.
- Why?: Reduces method call latency and improves execution speed.
- Example
public class MethodInliningExample { public static void main(String[] args) { long start = System.nanoTime(); for (int i = 0; i < 10_000_000; i++) { // JVM may inline this method System.out.println(add(i, i+1)); } long end = System.nanoTime(); System.out.println("Execution Time: " + (end - start) + " ns"); } static int add(int a, int b) { // Small method, likely to be inlined return a + b; } }
- Optimization in Action
- Detects
add()
as small and frequently used. - Instead of making method calls, it inserts the logic directly into the loop.
- This eliminates function call overhead and speeds up execution.
- Detects
- Optimization in Action
π 2: Loop Unrolling
- What?: Expands loop iterations to reduce loop control overhead (e.g., branching and index updates).
- Why?: Fewer iterations mean less CPU time wasted on loop conditions.
- Example
public class LoopUnrollingExample { public static void main(String[] args) { int sum = 0; // Instead of iterating 4 times // JVM may unroll this into direct additions for (int i = 0; i < 4; i++) { sum += i; } System.out.println(sum); } }
- Optimization in Action
- Loop may be re-written as:
sum += 0; sum += 1; sum += 2; sum += 3;
- This removes the loop condition check and improves performance.
- Loop may be re-written as:
- Optimization in Action
π 3: Escape Analysis
- What?: Determines whether an object escapes the current method and allocates it on the stack instead of the heap if possible.
- Why?: Reduces garbage collection overhead and improves memory efficiency.
- Example
import java.util.ArrayList; import java.util.List; public class EscapeAnalysisExample { public static void main(String[] args) { for (int i = 0; i < 1_000_000; i++) { // Likely optimized away by Escape Analysis List<Integer> numbers = createList(); } } static List<Integer> createList() { List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); return list; } }
- Optimization in Action
- Since
numbers
is not used outside the method, the JVM allocates it on the stack instead of the heap. - Stack allocation is faster and avoids unnecessary garbage collection.
- Since
- Optimization in Action
π 4: Dead Code Elimination (Compile time)
- What?: Remove code that has no effect on the final output.
- Why?: Reduces unnecessary computations, improving performance.
- Example
public class DeadCodeExample { public static void main(String[] args) { int x = 10; int y = 20; // This result is never used, // Remove it int z = x + y; System.out.println(x * y); } }
π 5: Constant Folding (Compile time)
- What?: Evaluate constant expressions at compile time instead of runtime.
- Why?: Reduces redundant computations during execution.
- Example
public class ConstantFoldingExample { public static void main(String[] args) { // Replaces this with '100' int x = 50 * 2; System.out.println(x); } }
- Optimization in Action
50 * 2
is replaced with100
, avoiding multiplication at runtime.
- Optimization in Action
Why does Java NOT do all optimizations at compile time? #
- What?: Java delays optimizations to make smart decisions at runtime!
- Some optimizations happen at compile time: Dead Code Elimination, Constant Folding, ..
- Most optimizations happen at runtime (JVM JIT Compiler): Escape Analysis, Loop Unrolling, Method Inlining..
- Why?
- 90-10 Rule: Java optimizes only frequently used parts to avoid unnecessary overhead
- Cross-Platform Execution: Compile time optimizations might not be efficient on all platforms.
- Preserving Runtime Information: Optimizing too early may remove valuable runtime insights.