热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java堆、栈、常量池和值传递、引用传递详解

先不要太关注参数到底是值传递还是引用传递,抛开这个想法,先搞清楚Java中值、对象、对象的引用是怎么存储的?栈:存放8种基本数据类型的变量和对象的引用(对象的引用保存的只是对象本身

先不要太关注参数到底是值传递还是引用传递,抛开这个想法,先搞清楚Java中值、对象、对象的引用是怎么存储的?


  • 栈:存放8种基本数据类型的变量和对象的引用(对象的引用保存的只是对象本身的地址),对象本身不存放在栈中,而是存放在堆和常量池中。
  • 堆:存放所有new出来的对象或数组。JVM不定时查看堆中的对象,如果没有引用指向这个对象就回收。
  • 常量池:存放字符串常量和基本类型常量(public static final)。

一、基本数据类型

基本数据类型的变量和常量:变量和引用存储在栈中,常量存储在常量池中。
以int为例:

int m1 = 7;  
int m2 = 7;
public static final int m3 = 7;
public static final int m4 = 7;

这里写图片描述
基本数据类型跟堆没有任何关系。


二、数组,字符串和其他引用类型

下面的例子对于数组,字符串和其他引用类型都是一样的,只是用字符串举例。
以String为例:
对于字符串:其对象的引用都是存储在栈中,对象本身如果是编译期已经创建好(直接用双引号赋值的:String s1 = “mistra1”; )的就存储在常量池中,如果是运行期(new出来的)就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。

String m1 = "mistra";  
String m2 = "mistra";
String m3 = new String("mistra");
String m4 = new String("mistra");

这里写图片描述
String m1 = “mistra”; ,这行代码被执行的时候,JVM首先在常量池中查找是否已经存在了值为”mistra”的这么一个对象,它的判断依据是String类equals(Object obj)方法的返回值。如果有,则不再创建新的对象,直接返回已存在对象的引用;如果没有,则先创建这个对象,然后把它加入到字符串池中,再将它的引用返回。

通过new产生一个字符串(假设为”mistra”)时,会先去常量池中查找是否已经有了”mistra”对象,如果没有则在常量池中创建一个此字符串对象,然后在堆中再创建一个常量池中此”mistra”对象的拷贝对象。
String s = new String(“mistra”);产生几个对象?一个或两个,如果常量池中原来没有”mistra”,就是两个。

  • 对于成员变量和局部变量:成员变量就是在方法外部,类的内部定义的变量;局部变量就是在方法或语句块内部定义的变量。局部变量必须初始化。 形式参数是局部变量,局部变量的数据存在于栈内存中。栈内存中的局部变量随着方法的消失而消失。 成员变量存储在堆中的对象里面,由垃圾回收器负责回收。

三、到底是值传递还是引用传递?

基本数据类型:值就直接保存在变量中。
引用类型:变量中保存的只是实际对象的地址。这里的变量就是对象的引用,引用指向实际对象,实际对象中保存着内容。

|>>>>>赋值运算符(=)的作用
赋值运算符对基本数据类型和引用类型的作用看下图就可以明白:
这里写图片描述
对于基本数据类型num ,赋值运算符会直接改变变量的值,原来的值被覆盖掉。
对于引用类型str,赋值运算符会改变引用中所保存的地址,原来的地址被覆盖掉。但是原来的对象(“mistra”)不会被改变。如上图所示,”mistra” 字符串对象没有被改变。(没有被任何引用所指向的对象是垃圾,会被垃圾回收器回收)

1、基本数据类型:
public class ParameterTransferTest1 {
public static void main(String[] args) {
int a = 3;
change(a);
System.out.println(a);
}
public static void change(int i) {
i =10;
}
}
输出结果:3

基本数据类型是值传递,传递的只是一个副本,与原值无关。 相当于把a的值3 copy给了 i,i 改变值并不会影响a的值。

2、没有提供改变自身方法的引用类型:

这里涉及到String与StringBuilder的知识点了。

public class ParameterTransferTest2 {
public static void main(String [] args){
String str = "a";
change(str);
System.out.println(str);
}
public static void change(String i) {
i = "b";
}
}
输出结果:a

每次改变String对象的值都是新创建了一个对象,原对象还在。而StringBuilder对象不同,每次改变都是在对象本身改变,不创建新对象。
假设str指向了地址x010,把这个地址引用传递给了 i,i 也指向x010,但是执行 i = “b”;之后,i 指向了新地址x011,并不影响str指向的地址,所以没有改变。

3、提供了改变自身方法的引用类型:
public class ParameterTransferTest3 {
public static void main(String [] args){
StringBuilder sb = new StringBuilder("a");
change(sb);
System.out.println(sb);
}
public static void change(StringBuilder i) {
i.append("b");
}
}
输出结果:ab

假设sb指向了地址x010,把这个地址引用传递给了 i,i 也指向x010,但是执行 i.append(“b”);之后,i 指向的地址不变,还是指向x010,所以影响了sb的值。


这里放几道例题加深理解:例题原博客地址

public class foo {
public static void main(String sgf[]) {
StringBuffer a=new StringBuffer(“A”);
StringBuffer b=new StringBuffer(“B”);
operate(a,b);
System.out.println(a+”.”+b);
}

static void operate(StringBuffer x,StringBuffer y) {
x.append(y);
y=x;
}
}

这里写图片描述

public class Example {    
String str = "abc";
char[] ch = { 'a', 'b', 'c' };
public static void main(String args[]) {
Example ex = new Example();
ex.change(ex.str, ex.ch);
System.out.println(ex.str);
System.out.println(ex.ch);
}
public void change(String str, char ch[]) {
*** // ch = new char[]{'a','b','c'};
str = "change";
ch[0] = 'c';
}
}
输出结果:
abc
cba

变量ch和引用传递是一样的,都是操作的为同一对象,如果有* * *的一行不注释的话,结果就为
abc
abc


推荐阅读
  • 本文详细介绍了Java反射机制的基本概念、获取Class对象的方法、反射的主要功能及其在实际开发中的应用。通过具体示例,帮助读者更好地理解和使用Java反射。 ... [详细]
  • 本文介绍了在 Java 编程中遇到的一个常见错误:对象无法转换为 long 类型,并提供了详细的解决方案。 ... [详细]
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • Java中不同类型的常量池(字符串常量池、Class常量池和运行时常量池)的对比与关联分析
    在研究Java虚拟机的过程中,笔者发现存在多种类型的常量池,包括字符串常量池、Class常量池和运行时常量池。通过查阅CSDN、博客园等相关资料,对这些常量池的特性、用途及其相互关系进行了详细探讨。本文将深入分析这三种常量池的差异与联系,帮助读者更好地理解Java虚拟机的内部机制。 ... [详细]
  • Flutter 2.* 路由管理详解
    本文详细介绍了 Flutter 2.* 中的路由管理机制,包括路由的基本概念、MaterialPageRoute 的使用、Navigator 的操作方法、路由传值、命名路由及其注册、路由钩子等。 ... [详细]
  • 在多线程并发环境中,普通变量的操作往往是线程不安全的。本文通过一个简单的例子,展示了如何使用 AtomicInteger 类及其核心的 CAS 无锁算法来保证线程安全。 ... [详细]
  • 使用Java 1.8 编译兼容1.6 JVM 的Class文件
    本文探讨了如何使用Java 1.8 编译器生成能够在1.6 JVM 上运行的Class文件,并介绍了Spring框架中的相关配置。 ... [详细]
  • 本文详细解析了Java类加载系统的父子委托机制。在Java程序中,.java源代码文件编译后会生成对应的.class字节码文件,这些字节码文件需要通过类加载器(ClassLoader)进行加载。ClassLoader采用双亲委派模型,确保类的加载过程既高效又安全,避免了类的重复加载和潜在的安全风险。该机制在Java虚拟机中扮演着至关重要的角色,确保了类加载的一致性和可靠性。 ... [详细]
  • Java并发机制详解及其在数据安全性保障中的应用方案 ... [详细]
  • 深入解析CAS机制:全面替代传统锁的底层原理与应用
    本文深入探讨了CAS(Compare-and-Swap)机制,分析了其作为传统锁的替代方案在并发控制中的优势与原理。CAS通过原子操作确保数据的一致性,避免了传统锁带来的性能瓶颈和死锁问题。文章详细解析了CAS的工作机制,并结合实际应用场景,展示了其在高并发环境下的高效性和可靠性。 ... [详细]
  • 本文探讨了如何利用Java代码获取当前本地操作系统中正在运行的进程列表及其详细信息。通过引入必要的包和类,开发者可以轻松地实现这一功能,为系统监控和管理提供有力支持。示例代码展示了具体实现方法,适用于需要了解系统进程状态的开发人员。 ... [详细]
  • 使用Maven JAR插件将单个或多个文件及其依赖项合并为一个可引用的JAR包
    本文介绍了如何利用Maven中的maven-assembly-plugin插件将单个或多个Java文件及其依赖项打包成一个可引用的JAR文件。首先,需要创建一个新的Maven项目,并将待打包的Java文件复制到该项目中。通过配置maven-assembly-plugin,可以实现将所有文件及其依赖项合并为一个独立的JAR包,方便在其他项目中引用和使用。此外,该方法还支持自定义装配描述符,以满足不同场景下的需求。 ... [详细]
  • 本文介绍了如何利用 Delphi 中的 IdTCPServer 和 IdTCPClient 控件实现高效的文件传输。这些控件在默认情况下采用阻塞模式,并且服务器端已经集成了多线程处理,能够支持任意大小的文件传输,无需担心数据包大小的限制。与传统的 ClientSocket 相比,Indy 控件提供了更为简洁和可靠的解决方案,特别适用于开发高性能的网络文件传输应用程序。 ... [详细]
  • 本文深入探讨了Java多线程环境下的同步机制及其应用,重点介绍了`synchronized`关键字的使用方法和原理。`synchronized`关键字主要用于确保多个线程在访问共享资源时的互斥性和原子性。通过具体示例,如在一个类中使用`synchronized`修饰方法,展示了如何实现线程安全的代码块。此外,文章还讨论了`ReentrantLock`等其他同步工具的优缺点,并提供了实际应用场景中的最佳实践。 ... [详细]
author-avatar
sweeteenring
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有