Java中的栈和堆总结

本文详细解释了Java中的栈和堆的概念,包括它们各自的特点、应用场景以及如何进行内存分配。此外,还探讨了String对象在栈和堆中的存储方式,以及如何通过new关键字创建对象。

栈与堆都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。 

Java的堆是一个运行时数据区,类的对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。 

栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

栈中主要存放一些基本类型的变量(int, short, long, byte, float, double, boolean, char)和对象句柄。 

栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义: 

int a = 3; 

int b = 3; 

编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。 

这时,如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。 

要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。

String是一个特殊的包装类数据。可以用: 

String str = new String("abc"); 

String str = "abc"; 

两种的形式来创建,第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。 

而第二种是先在栈中创建一个对String类的对象引用变量str,然后查找栈中有没有存放"abc",如果没有,则将"abc"存放进栈,并令str指向”abc”,如果已经有”abc” 则直接令str指向“abc”。 

比较类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==,下面用例子说明上面的理论。 

String str1 = "abc"; 

String str2 = "abc"; 

System.out.println(str1==str2); //true 

可以看出str1和str2是指向同一个对象的。 

String str1 =new String ("abc"); 

String str2 =new String ("abc"); 

System.out.println(str1==str2); // false 

用new的方式是生成不同的对象。每一次生成一个。 

因此用第二种方式创建多个”abc”字符串,在内存中其实只存在一个对象而已. 这种写法有利与节省内存空间. 同时它可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。 

另一方面, 要注意: 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的对象。只有通过new()方法才能保证每次都创建一个新的对象。 由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。

 

### Java内存与内存的区别及用途 #### 内存 (Stack Memory) 内存主要用于存储局部变量方法调用的上下文信息。它是一种线程私有的内存区域,每个线程都有自己的空间。内存的特点如下: - **用途**: 主要用于存储方法调用过程中的临时数据,包括方法参数、局部变量以及返回地址等[^4]。 - **分配方式**: 内存的分配回收是自动完成的。每当一个方法被调用时,会为其创建一个新的帧(Stack Frame),并将该帧压入当前线程的中;当方法执行完成后,对应的帧会被弹出并释放。 - **生命周期**: 内存中的数据生命周期较短,仅在方法调用期间有效。一旦方法结束,其关联的帧就会被销毁[^2]。 - **性能特点**: 由于的操作遵循先进后出(LIFO)原则,因此内存分配释放的速度非常快。 #### 内存 (Heap Memory) 内存是一个由所有线程共享的全局内存区域,主要用于存储对象实例其他动态分配的数据结构。以下是内存的主要特征: - **用途**: 内存主要用于存储通过 `new` 关键字或其他动态分配机制创建的对象实例。这些对象可以在多个线程之间共享。 - **分配方式**: 对象的内存分配是由垃圾回收器(Garbage Collector, GC)管理的。这意味着开发人员无需显式地释放上的内存资源,而是依赖于GC来清理不再使用的对象。 - **生命周期**: 内存中的对象通常具有较长的生命周期,只有在其没有任何引用指向时才可能被GC回收。 - **性能特点**: 虽然提供了更大的存储容量,但由于需要进行复杂的内存管理垃圾回收操作,其分配释放速度相对较慢。 #### 总结对比表 | 特性 | 内存 (Stack) | 内存 (Heap) | |-----------------|-----------------------------------|------------------------------------| | 存储内容 | 局部变量、方法参数 | 对象实例 | | 内存大小 | 较小 | 较大 | | 生命周期 | 方法调用期间短暂存在 | 可能跨越多个方法甚至整个应用周期 | | 访问权限 | 私有(线程独占) | 共享(多线程间可访问) | | 分配/释放 | 自动 | 手动或由GC处理 | ```java // 示例代码展示的关系 public class StackAndHeapExample { public static void main(String[] args) { int stackVariable = 10; // 这个整数存储在上 Person heapObject = new Person(); // 'Person' 实例存储在上,而引用存储在上 System.out.println(heapObject.getName()); changeValue(stackVariable); // 修改的是副本,不影响原值 modifyObject(heapObject); // 修改的是实际对象的内容 } private static void changeValue(int value){ value += 5; } private static void modifyObject(Person person){ person.setName("Modified Name"); } } class Person{ private String name; public String getName() {return this.name;} public void setName(String newName){this.name=newName;} } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值