Java 堆和栈

本文深入解析Java中的堆和栈的区别,详细讲解对象的创建过程、构造函数的作用以及内存分配机制。通过具体实例,展示如何通过new关键字产生对象,构造函数的调用及参数传递,以及引用与对象之间的关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

Java 堆和栈

在程序运行的时候,都是在内存中分配空间的,在空间中存放对象等,内存中分为2中区域,堆(heap) 和 栈(stack),堆和栈的是由JVM进行分配的。

 

方法的调用和局部变量都是放在栈中的,当方法执行完后栈中的资源就会被释放掉。

产生的对象及对象中的实例变量都是放在堆上。当某个对像没有线程使用或者没有引用指向它的时候,它就满足了垃圾回收的条件。

 

在java程序中时常会产生某个对象的实例,如:

 

Person p = new Person();

 

以上这段非常简单的代码完成了某个对象的创建,但是当调用new Person(); 实质上调用了构造函数。此处的小写p,是一个引用,它指向了堆中的Person对象。

 

构造函数是在被赋值给引用之前得到调用,“=”,就是赋值操作,底层是把产生的Person 对象的内存首地址赋值给了引用p,构造函数介入了new 的过程,也就是产生Person对象的过程来完成初始化的作用。

 

构造函数语法规则如下:

语法格式:

< modifiers>  <class_name>([< argu_list>]) {

             [< statements>]

}

举例:

class Person {

int age;

Person() { //不带参数的构造函数

 age = 18;  

}

Person(int i) {

age = i;// 带参数的构造函数

 }

void setAge(int i) {

age = i;

}

}

 

在一个类中可以有多个参数不同的构造函数,实际就是构造函数的重载。在调用产生对象的时候,可以根据不同的实际情况调用不同的构造函数,比如某个系统,要求登陆者输入年龄,但不是必须的,如果登陆者填写了自己的年龄,则调用Person(int i) ,如果没有输入年龄则调用Person() 在这个构造函数中会自动的给age 赋值为18。

 

Java语言中,每个类都至少有一个构造方法。如果类的定义者没有显式的定义任何构造方法,系统将自动提供一个默认的构造方法,系统提供的默认方法,具有1. 默认构造方法没有参数。2.默认构造方法没有方法体

Java类中,一旦类的定义者显式定义了一个或多个构造方法,系统将不再提供默认的构造方法。

 

类的定义:

class Person {

           int age;

           void setAge(int w) { age = w; }

}

等价于:  

 class Person{

           int age

           Person() {}

           void setAge(int w) { age = w; }

}

 

应用举例:

class Test{

  public static void main(String args[]){

        Person d = new Person() ;

        d.setAge(120);

  }

  }

 

 

构造方法重载

构造方法重载举例:

   public class Person{

         public Person(String name, int age, String s) {…}

         public Person(String name, int age) {…}

         public Person(String name, String s) {…}

  }

构造方法重载,参数列表必须不同

可以在构造方法的第一行使用this关键字调用其它(重载的)构造方法

 

构造方法重载举例:

public class Person {

            private String name;

            private int age;

            private String sex;

            public Person(String name, int age, String s) {

        this.name = name;

        this.age = age;

        this.sex= s;

          }

          public Person(String name, int age) {

        this(name, age, “female”);

          }

          public Person(String name, String d) {

        this(name, 30, d);

          }

          public Person(String name) {

          this(name, 30);

          }

}

 

 

 

Java 内存的分配

内存从比较详细的角度可分四个区域:

 

code segment

主要存放代码。

data segment

主要存放静态变量和字符串常量

Stack

主要存放局部变量和执行方法

Heap

主要存放new出来的对象或者数组,数组其实也是对象,大家可以认为只要有new 关键字,就是对象,比如需要创建一个int 类型数组,需要 int[] I = new int[5]。

 

比如当我们执行了Person p = new Person(), 其作用是通过调用构造函数产生了一个Person对象,并且有引用p指向它。在内存中分配如下:

 

 

 

以上是不带参数的构造函数,如果是带参数的构造函数,则涉及到实例变量和局部变量的赋值过程,看以下实例:

构造函数

public class Person{

private String pname;

public int age;

Public Person(String pname,int age){

this.pname = pname;

this.age = age;

}

}

public static void main(String args[]){

person p0 = new Person(“test0”,5);

}

 

当运行入口函数的时候,内存中的执行过程如下:

1.       先分配空间

2.       构造函数后被实例变量被构造函数中的局部变量赋值

3.       构造函数执行完后局部变量消失

4.       引用指向对象

 

在构造函数Person(String pname,int age) 中的pname 和 age 是局部变量,构造方法外部的private String pname; public int age; 是实例变量, 当调用person p0 = new Person(“test0”,5),是先给这两个局部变量进行了赋值操作。

 

然后根据

构造函数中的this.pname = pname; this.age = age; 把局部变量的值赋给了实例变量内存结构图如下

 

 

 

然后构造方法执行完毕左边栈中pname 和age 就消失了,通过执行person p0 = new Person(“test0”,5) 中的“=”,对象的内存首地址就被赋值了p0 引用,内存结构图如下:

 

 

传值

看以下实例

public class Dog {

private String dogName;

private int age;

public Dog(String dogName,int age) {

this.dogName = dogName;

this.age = age;

}

 

 

public class MemoryDemo {

 

   

    public static void main(String[] args) {

       Dog dog1 = new Dog("xiaobai",5);

       Dog dog2 = new Dog("xiaohei",6);

       MemoryDemo md = new MemoryDemo();

       int date = 9;

       md.change3(date);

       System.out.println("date:"+date);

      

       md.change2(dog1);

       System.out.println("dog1age"+dog1.getAge());

      

      

       md.change1(dog1);

       System.out.println("dogName:"+dog1.getAge());

 

 

    }

   

   

    public void change3(int date){

      

       date = 10;

      

    }

 

    public void change2(Dog dog1){

      

       dog1.setAge(8);

      

    }

   

    public void change1(Dog dog1){

      

       dog1 = new Dog();

      

       dog1.setAge(9);

      

    }

}

 

运行程序打印输出:

 

date:9

dog1age8

dogName:8

 

 

实际上在java 中传递参数的时候都是传值操作,java 中没有传递引用,有的书上会提到传递 引用,实际上是为了好理解。

 

Java中传值操作实际上是对要传的值做了一份拷贝,基本数据与引用都是一样的。

 

在上面的例子中首先定义了 int date = 9;    然后调用了 md.change3(date),在运行的时候是把date 值在栈上做了一份拷贝操作,对新拷贝的date 赋值为10,原本存在的date仍然是9。

 

在执行change2 的时候,实际上是对引用做了一份拷贝,但是由于两个引用指向了堆中同样的一个对象,所以,当修改被拷贝引用指向的对象值的时候,原本的引用指向的对象也发生了改变。

 

在执行change1的时候,实际上是对引用做了一份拷贝,但是拷贝出来的引用又指向了新对象,与被拷贝的引用指向的对象是不一样的,所以修改后的对象的时候对前者指向的对象没有影响。

 

 

。。。。。。String

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值