-
-
Notifications
You must be signed in to change notification settings - Fork 211
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1987 from riyaa060/memory
Memory Management in Java (JVM Internals)
- Loading branch information
Showing
1 changed file
with
112 additions
and
97 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,110 +1,125 @@ | ||
--- | ||
id: Polymorphism-in-java | ||
id: memory-management | ||
sidebar_position: 10 | ||
title: "Polymorphism in Java" | ||
sidebar_label: "Polymorphism in Java" | ||
title: "Memory management in Java" | ||
sidebar_label: "Memory management" | ||
--- | ||
# Polymorphism in Java | ||
|
||
Welcome to the Java Programming repository! This section focuses on one of the core Object-Oriented Programming (OOP) concepts: **Polymorphism**. | ||
# Memory Management in Java (JVM Internals) | ||
|
||
Polymorphism allows methods to perform different tasks based on the object calling them, providing flexibility and reusable code. In Java, polymorphism is mainly achieved through **method overriding** and **method overloading**. | ||
Memory management is a critical aspect of the Java Virtual Machine (JVM). The JVM is responsible for managing the memory resources of a Java application, including allocation, garbage collection, and deallocation of objects. The JVM's memory management model is designed to optimize performance, scalability, and reliability. | ||
|
||
## Table of Contents | ||
- [Introduction to Polymorphism](#introduction-to-polymorphism) | ||
- [Types of Polymorphism](#types-of-polymorphism) | ||
- [Compile-Time Polymorphism (Method Overloading)](#compile-time-polymorphism-method-overloading) | ||
- [Run-Time Polymorphism (Method Overriding)](#run-time-polymorphism-method-overriding) | ||
- [Code Examples](#code-examples) | ||
- [Conclusion](#conclusion) | ||
1. [JVM Memory Architecture](#jvm-memory-architecture) | ||
2. [Memory Areas in JVM](#memory-areas-in-jvm) | ||
- [Heap](#heap) | ||
- [Stack](#stack) | ||
- [Program Counter (PC) Register](#program-counter-pc-register) | ||
- [Method Area](#method-area) | ||
- [Native Method Stack](#native-method-stack) | ||
3. [Garbage Collection](#garbage-collection) | ||
- [Generational Garbage Collection](#generational-garbage-collection) | ||
- [Garbage Collectors](#garbage-collectors) | ||
4. [Memory Management Strategies](#memory-management-strategies) | ||
- [Memory Pooling](#memory-pooling) | ||
- [Object Allocation and Deallocation](#object-allocation-and-deallocation) | ||
5. [Conclusion](#conclusion) | ||
|
||
--- | ||
|
||
## Introduction to Polymorphism | ||
|
||
Polymorphism, a Greek term meaning "many forms," allows Java methods to take on multiple forms. This means a single function can handle different data types or perform different tasks based on the context, enhancing code flexibility and scalability. | ||
|
||
Polymorphism in Java can be divided into two main types: | ||
|
||
1. **Compile-Time Polymorphism**: Achieved through method overloading. | ||
2. **Run-Time Polymorphism**: Achieved through method overriding. | ||
|
||
## Types of Polymorphism | ||
|
||
### 1. Compile-Time Polymorphism (Method Overloading) | ||
|
||
**Method Overloading** allows multiple methods in the same class to have the same name but different parameters. The compiler determines the correct method to call based on the method signature (number and type of parameters). | ||
|
||
Example: | ||
```java | ||
public class Calculator { | ||
// Method to add two integers | ||
public int add(int a, int b) { | ||
return a + b; | ||
} | ||
|
||
// Method to add three integers | ||
public int add(int a, int b, int c) { | ||
return a + b + c; | ||
} | ||
} | ||
``` | ||
In the `Calculator` class, we have two `add` methods: | ||
|
||
- `add(int a, int b)` adds two integers. | ||
- `add(int a, int b, int c)` adds three integers. | ||
|
||
### 2. Run-Time Polymorphism (Method Overriding) | ||
|
||
**Method Overriding** occurs when a subclass provides a specific implementation of a method already defined in its superclass. This type of polymorphism enables a child class to alter the behavior of its parent class. | ||
|
||
Example: | ||
|
||
```java | ||
class Animal { | ||
// Method to describe sound of an animal | ||
public void sound() { | ||
System.out.println("Some generic animal sound"); | ||
} | ||
} | ||
|
||
class Dog extends Animal { | ||
// Method to describe sound of a dog (overrides the parent method) | ||
@Override | ||
public void sound() { | ||
System.out.println("Woof Woof"); | ||
} | ||
} | ||
|
||
class Cat extends Animal { | ||
// Method to describe sound of a cat (overrides the parent method) | ||
@Override | ||
public void sound() { | ||
System.out.println("Meow Meow"); | ||
} | ||
} | ||
``` | ||
In this example: | ||
|
||
- The `Animal` class has a method `sound()` that prints a generic message. | ||
- The `Dog` and `Cat` classes override the `sound()` method with their unique implementations. | ||
|
||
### Run-Time Polymorphism in Action: | ||
|
||
```java | ||
public class Main { | ||
public static void main(String[] args) { | ||
Animal myDog = new Dog(); // Polymorphic object | ||
Animal myCat = new Cat(); // Polymorphic object | ||
|
||
myDog.sound(); // Outputs: Woof Woof | ||
myCat.sound(); // Outputs: Meow Meow | ||
} | ||
} | ||
``` | ||
Here, the method that gets executed depends on the object type at runtime.` myDog` calls the `Dog` class's version of `sound()`, while `myCat` calls the `Cat` class's version. | ||
## JVM Memory Architecture | ||
|
||
The JVM's memory management is split into various memory areas that serve different functions. These areas interact with each other to ensure efficient memory usage. The architecture includes both the **heap** and **non-heap** areas. | ||
|
||
### Memory Areas in JVM | ||
|
||
1. **Heap**: This is the runtime data area where objects are allocated. The heap is shared by all threads in the JVM. | ||
2. **Stack**: Each thread has its own stack, where local variables and method calls are stored. | ||
3. **Program Counter (PC) Register**: Each thread has its own PC register, which tracks the currently executing instruction in the thread. | ||
4. **Method Area**: Stores class-level data, including bytecode, method and field information, and static variables. | ||
5. **Native Method Stack**: Used for the execution of native methods (those written in languages like C or C++). | ||
|
||
--- | ||
|
||
### Heap | ||
|
||
The **heap** is where Java objects are allocated. It is created when the JVM starts and can dynamically grow and shrink as the application runs. The heap is divided into multiple regions: | ||
|
||
- **Young Generation**: Where new objects are allocated. This space is further divided into: | ||
- **Eden Space**: Initial area for new objects. | ||
- **Survivor Spaces (S0, S1)**: For objects that survived garbage collection in the Young Generation. | ||
|
||
- **Old Generation (Tenured Generation)**: Holds objects that have survived multiple garbage collection cycles in the Young Generation. Objects that are long-lived are eventually moved to this space. | ||
|
||
- **Permanent Generation (PermGen) / Metaspace** (in Java 8 and beyond): Stores class metadata, method data, and static variables. | ||
|
||
### Stack | ||
|
||
Each thread in the JVM has its own **stack**, which is used to store local variables and method call frames. When a method is invoked, a new frame is pushed onto the stack, and when the method completes, the frame is popped off. | ||
|
||
- **Local Variables**: Store the parameters and local variables within a method. | ||
- **Method Call Frames**: Contain information about method invocations, such as the return address and the method’s local variables. | ||
|
||
### Program Counter (PC) Register | ||
|
||
Each thread has its own **PC register**. This register keeps track of the address of the current instruction being executed by the thread. In a multi-threaded environment, each thread has its own PC register to ensure that each thread's execution is independent. | ||
|
||
### Method Area | ||
|
||
The **method area** stores class-level information that is shared across all threads in the JVM: | ||
|
||
- **Class Metadata**: Information about the classes, interfaces, and methods. | ||
- **Static Variables**: Variables that belong to the class rather than to any instance of the class. | ||
- **Method Code**: The bytecode for the methods of the classes. | ||
|
||
### Native Method Stack | ||
|
||
The **native method stack** is used for executing **native methods** (i.e., methods written in languages like C or C++). This stack is similar to the Java stack but is specifically dedicated to native code execution. | ||
|
||
--- | ||
|
||
## Garbage Collection | ||
|
||
Garbage collection (GC) is the process of reclaiming memory used by objects that are no longer reachable by the program. The JVM performs automatic garbage collection to free up memory and avoid memory leaks. | ||
|
||
### Generational Garbage Collection | ||
|
||
The JVM employs a **generational garbage collection** strategy based on the hypothesis that most objects are short-lived. Objects are categorized into the following generations: | ||
|
||
- **Young Generation**: Newly created objects are allocated here. It is subject to frequent garbage collections, as most objects quickly become unreachable and are reclaimed. | ||
- **Old Generation**: Objects that have survived multiple garbage collection cycles in the Young Generation are promoted to the Old Generation. GC in this space is less frequent but more costly. | ||
- **Permanent Generation / Metaspace**: Stores metadata like class definitions. In Java 8 and later, the Permanent Generation was replaced by Metaspace, which grows dynamically based on the system memory. | ||
|
||
### Garbage Collectors | ||
|
||
The JVM uses several types of garbage collectors, each designed for different workloads and application types: | ||
|
||
1. **Serial Garbage Collector**: A simple GC suitable for small applications with a single thread. | ||
2. **Parallel Garbage Collector (Throughput Collector)**: Designed for multi-threaded environments. It aims to minimize application pause times. | ||
3. **Concurrent Mark-Sweep (CMS) Collector**: A low-latency garbage collector that aims to minimize pause times by performing most of its work concurrently with application threads. | ||
4. **G1 Garbage Collector**: A more modern garbage collector that divides the heap into regions and collects garbage in a manner that minimizes pause times. It is the default collector in newer JVM versions. | ||
|
||
--- | ||
|
||
## Memory Management Strategies | ||
|
||
### Memory Pooling | ||
|
||
Memory pooling is a strategy where objects or resources are pre-allocated and reused instead of being created and destroyed frequently. This helps reduce the overhead of memory allocation and deallocation, particularly in high-performance applications. Common techniques include: | ||
|
||
- **Object Pooling**: Reusing frequently used objects rather than allocating and deallocating them. | ||
- **Buffer Pools**: Reusing memory buffers for I/O operations. | ||
|
||
### Object Allocation and Deallocation | ||
|
||
In Java, object allocation is typically done through the `new` keyword. When an object is no longer referenced, it becomes eligible for garbage collection. However, the JVM will not immediately reclaim the memory of unreachable objects. The **garbage collector** runs in the background and identifies objects that can be safely deallocated. | ||
|
||
- **Allocation**: Objects are allocated in the **Young Generation** of the heap. | ||
- **Deallocation**: Objects are deallocated when they become unreachable, and the garbage collector reclaims their memory. | ||
|
||
|
||
## Conclusion | ||
Polymorphism is a powerful feature in Java that increases flexibility and readability. By using method overloading and overriding, we can write more dynamic and adaptable code. Try experimenting with these examples to deepen your understanding of how polymorphism works in Java! | ||
|
||
Happy Coding (✿◠‿◠) | ||
Memory management in Java is a complex and critical aspect of JVM internals. By understanding the JVM's memory architecture, including the heap, stack, and various memory regions, as well as how garbage collection works, developers can write more efficient and scalable applications. Awareness of memory leaks and the tools available to mitigate them is also key to maintaining optimal performance. | ||
|
||
By selecting the right garbage collector, optimizing object allocation, and monitoring memory usage, Java applications can achieve efficient memory utilization and maintain stability during runtime. |