Java 中的内存管理
什么是栈内存?
Java中的栈(Stack)是内存中用于包含方法、局部变量和引用变量的部分。栈内存始终以“后进先出”(LIFO)的顺序进行引用。局部变量在栈中创建。
什么是堆内存?
堆(Heap)是内存中用于包含对象的部分,也可能包含引用变量。实例变量在堆中创建。
Java中的内存分配
Java中的内存分配是指将程序的虚拟内存部分划分为特定区域,用于存储变量以及结构和类的实例。然而,内存并非在声明时分配给对象,而仅仅是创建一个引用。对象的内存分配使用`new()`方法,因此对象总是分配在堆内存上。
Java内存分配分为以下几个部分:
- 堆
- Stack
- 代码
- 静态
这种内存划分是为了其有效的管理。
- 代码部分包含您的字节码。
- 内存的栈部分包含方法、局部变量和引用变量。
- 堆部分包含对象(也可能包含引用变量)。
- 静态部分包含静态数据/方法。
局部变量和实例变量的区别
实例变量声明在类内部,但不在方法内部。
class Student{ int num; // num is instance variable public void showData{}
局部变量声明在方法内部,包括方法参数。
public void sum(int a){ int x = int a + 3; // a , x are local variables; }
栈和堆的区别
如果视频无法访问,请点击此处
我们用一个例子来更好地理解这一点。假设我们的main方法调用了m1方法
public void m1{ int x=20 }
在Java的栈中,会为m1方法创建一个栈帧。
m1中的变量X也会在栈中m1的栈帧里创建。(见下图)。
方法m1调用方法m2。在Java的栈中,会在m1栈帧的顶部创建一个新的m2栈帧。
变量b和c也会在栈中m2的栈帧里创建。
public void m2(int b){ boolean c; }
同样,方法m2调用方法m3。又会在栈顶创建一个m3栈帧(见下图)。
现在假设我们的m3方法正在创建一个“Account”类的对象,该类有两个实例变量 int p和int q。
Account { Int p; Int q; }
这是m3方法的代码:
public void m3(){ Account ref = new Account(); // more code }
语句 `new Account()` 会在堆中创建一个Account对象。
引用变量“ref”会在Java的栈中创建。
赋值“=”运算符将使引用变量指向堆中的对象。
一旦方法完成了它的执行。控制流将返回到调用方法。在本例中是m2方法。
来自m3方法的栈会被清空。
由于引用变量将不再指向堆中的对象,它将被垃圾回收。
一旦m2方法完成了它的执行。它将被从栈中弹出,它的所有变量都将被清除,并且不再可用。
对m1方法也同样如此。
最终,控制流将返回到程序的起始点。通常是“main”方法。
如果对象将其实例变量作为引用怎么办?
public static void main(String args[]) { A parent = new A(); //more code } class A{ B child = new B(); int e; //more code } class B{ int c; int d; //more code }
在这种情况下,引用变量“child”将在堆中创建,它又会指向它的对象,类似于下图所示。
Java中的垃圾回收是什么?
Java垃圾回收是一个程序自动执行内存管理的过程。垃圾收集器(GC)会查找未使用的对象并删除它们以回收内存。在Java中,对象的动态内存分配是通过`new`运算符实现的,它会占用一些内存,并且直到有引用指向该对象时,内存才会一直分配。
当没有对象引用时,它就被认为不再需要了,并且可以回收对象占用的内存。由于Java会自动处理内存释放,因此没有必要显式销毁对象。
实现这一目标的技术被称为垃圾回收。不释放内存的程序最终可能会因系统没有剩余内存可分配而崩溃。这些程序被称为存在内存泄漏。Java中的垃圾回收在程序生命周期中自动发生,无需手动释放内存,从而避免内存泄漏。
在C语言中,程序员有责任使用`free()`函数释放动态分配的内存。这正是Java内存管理优于C语言的地方。
注意:所有对象都在内存的堆部分创建。更多内容将在之后的教程中介绍。
示例:学习Java垃圾回收机制
步骤 1) 将以下代码复制到编辑器中。
class Student{ int a; int b; public void setData(int c,int d){ a=c; b=d; } public void showData(){ System.out.println("Value of a = "+a); System.out.println("Value of b = "+b); } public static void main(String args[]){ Student s1 = new Student(); Student s2 = new Student(); s1.setData(1,2); s2.setData(3,4); s1.showData(); s2.showData(); //Student s3; //s3=s2; //s3.showData(); //s2=null; //s3.showData(); //s3=null; //s3.showData(); } }
步骤2)保存、编译并运行代码。如图所示,创建了两个对象和两个引用变量。
步骤3)取消注释第20、21、22行。保存、编译并运行代码。
步骤4)如下图所示,两个引用变量指向同一个对象。
步骤5)取消注释第23行和第24行。编译、保存并运行代码。
步骤6)如下图所示,s2变为null,但s3仍然指向该对象,并且不符合Java垃圾回收的条件。
步骤7)取消注释第25行和第26行。保存、编译并运行代码。
步骤8)此时,没有任何引用指向该对象,它已符合垃圾回收条件。它将被从内存中移除,并且无法找回。
如何在Java中删除一个对象?
1)如果你想让你的对象符合垃圾回收条件,请将其引用变量赋值为null。
2)基本类型不是对象。它们不能赋值为null。
摘要
- 当调用一个方法时,会在栈顶创建一个栈帧。
- 一旦方法执行完毕,控制流将返回到调用方法,其相应的栈帧将被清除。
- 局部变量在栈中创建。
- 实例变量在堆中创建,是其所属对象的一部分。
- 引用变量在栈中创建。