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

  1. Writing Machine Code
  2. Using Assembly Language
  3. Structured Programming with C Language
  4. OOPS with C++
  5. OOPS with Java

πŸ“Œ 1: Writing Machine Code

  • Machine Code: Consists of binary instructions (0s and 1s) 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, and JMP
  • 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.

πŸ“Œ 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

πŸ“Œ 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!")
          }
    • 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!")
            }
        }
    • Groovy: Dynamic scripting language with Java-like syntax.
      • Example:
        println "Hello from Groovy!"
    • Others: Jython (Python on JVM), Clojure, ..

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.*)
  • 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 overrides findClass().
  • 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.

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 the javac compiler.
    • Interpreted: The JVM interprets bytecode and executes it line by line.
  • Process Flow
    • Java Source Code (.java) β†’ Compilation (javac) β†’ Bytecode (.class)
    • Bytecode (.class) β†’ JVM Interprets & Executes

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, ...).

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;
          }
      }

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.

πŸ“Œ 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.

πŸ“Œ 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.

πŸ“Œ 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 with 100, avoiding multiplication at runtime.

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.