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.