Java中类,对象,方法的内存分配

如下针对引用数据类型:
在内存中,类是静态的概念,它存在于内存中的CodeSegment中。
当咱们使用new关键字生成对象时,JVM根据类的代码,去堆内存中开辟一块控件,存放该对象,该对象拥有一些属性,拥有一些方法。可是同一个类的对象和对象之间并非没有联系的,看下面的例子:数组

class Student{
    static String schoolName;
    String name;
    int age;函数

    void speak(String name){
        System.out.println(name);
    }布局

    void read(){
    }
}this

class Test{
    public statis void main(String[] args){
        Student a = new Student();
        Student b = new Student();
    }
}spa

在上面的例子中,生成了两个Student对象,两个对象拥有各自的name和age属性,而schoolName属性因为是static的,被全部的对象所共有,并且每一个对象都有权更改这个属性。
而对于方法speak()和read()来说,它们被全部的对象所共有,但并非说哪一个对象拥有这些方法。方法仅仅是一种逻辑片断而已,它不是实物,严格来说不能被拥有。
方法存在的意义就是对对象进行修改。(个人理解)
上述speak()方法,虽然两个对象都拥有相同的方法,可是因为其操做的对象不一样,因此执行起来的效果也不一样。再说一次,方法是看不见摸不着的,它仅仅是一种逻辑操做!只有看成用于具体的对象时,方法才具体化了!
方法在不执行的时候不占用内存空间,只有在执行的时候才会占用内存空间。
就比如说一我的会翻跟斗,他翻跟斗的时候是须要空间的,可是他不翻跟斗的时候是不须要额外的空间的。可是无论他翻不翻跟斗,他始终是具备翻跟斗的技能的

Java中的内存布局(其余面向对象的语言也是如此)code

Java中的内存空间分为四种:
1. code segment(代码段,是否是所谓的方法区?)
存储class文件的内容,即咱们写的代码。
2. data segment(数据段)
存储静态变量
3. heap segment
堆空间,存储new出来的对象
4. stack segment
栈空间,存储引用类型的引用(注意,这里存储的不必定是对象所处的物理地址,可是必定可以根据这个内容在堆中找到对应的对象),局部变量和方法执行时的方法的代码对象

以上面的speak(String name)方法的调用为例来分析下内存:
调用该方法时,首先在栈控件开辟了一块区域存放name引用。而后将传入的那个对象的“地址”赋值给这个引用。因而出现了什么状况?两个引用指向同一个对象。而咱们操做对象时是经过对象的引用来执行操做,因此当一个对象有一个以上的引用同时指向它时,就会出现一些比较混乱的事情了。内存

经过马士兵在课上讲的一个小例子来看看:get

public class Test {class

    public static void main(String[] args) {
        Test test = new Test();
        int data = 10;
        BirthDate b1 = new BirthDate(5,4,1993);
        BirthDate b2 = new BirthDate(25,5,1992);

        test.change(data);
        test.change1(b1);
        test.change2(b2);
        System.out.println(data);
        b1.display();
        b2.display();
    }

    void change(int i){
        i = 100;
    }

    void change1(BirthDate b){
        b = new BirthDate(1,1,1);
    }

    void change2(BirthDate b){
        b.setDay(100);
    }
}

class BirthDate{
    int day;
    int month;
    int year;

    BirthDate(int day,int month,int year){
        this.day = day;
        this.month = month;
        this.year = year;
    }

    public int getDay() {
        return day;
    }
    public void setDay(int day) {
        this.day = day;
    }
    public int getMonth() {
        return month;
    }
    public void setMonth(int month) {
        this.month = month;
    }
    public int getYear() {
        return year;
    }
    public void setYear(int year) {
        this.year = year;
    }

    public void display(){
        System.out.println("day = "+day+"\nmonth = "+month+"\nyear = "+year);
    }
}

    输出为:

    10
    day = 5
    month = 4
    year = 1993
    day = 100
    month = 5
    year = 1992

从输出结果来看看发生了什么:
1. 调用change(int i)
此时在栈空间中新建了一个i,并把data的值复制给i。这个时候在栈空间中有两个int类型的数,两者的值虽然都为10,可是两者毫无关系,栈空间中有两个10.
在方法体中对i进行赋值,该操做是对i进行的,并不影响data,因此当方法结束时data仍是原来的data,连地址都没变一下。同时在方法结束时i自动从栈空间中消失。
2. 调用change1(BirthDate b)
此时在栈空间中新建一个引用b,在调用该方法的时候,将传进来的引用的值复制给b,即b和b1拥有相同的内容,指向同一个对象。在方法体中对b又进行赋值操做,首先在堆空间中new出一个新的对象,而后将b改成指向这个新的对象。该操做也未影响b1。方法结束后,引用b消失,刚才new出来的新对象成了垃圾,等待GC的回收。
3. 调用change2(BirthDate b)
同上,先在对中新建一个引用,名为b,而后将其指向b2所指向的对象。注意,此时调用了该对象的方法,修改了部分属性,因此此操做改变了这个对象,而b2也指向这个对象,因此最后b2的输出发生了变化。

从上面的三个方法能够看出,当方法中的参数列表不为空时:
- 若是参数是引用数据类型,该方法执行的过程首先是建立若干个引用,而后将这些引用的值和传进来的引用的值一一对应复制。复制完以后,传进来的参数(引用)的工做也就完成了。
- 若是参数是基本数据类型,那么首先在栈中建立对应数量个变量,将这些变量的值和传进来的参数一一对应复制。复制完以后也没有外面什么事了。

综上:传参时要注意,若是对传进去的参数(其实是引用)进行了从新的赋值操做,那么该方法应该有一个返回值,不然该方法是没有意义的,如同上面的change1()。

    方法通常有两个做用:1. 对某变量进行改变。 2. 根据传进来的参数返回另外一变量。
    若是一个方法不想有返回值,只是想对某变量进行改变,不要将该对象做为参数传进去,而直接在方法中得到其访问权限而后直接更改。方法的参数列表为空。
    若是一个方法要有返回值,最好先在方法内部new一个临时变量,先将传进来的参数复制一下,逻辑执行完后,把临时变量return出去。

20170620更新:
其实以上问题涉及到的东西是值传递与引用传递。在C++中两者都有,可是在Java中只有值传递。具体到实践中分两种状况:
- 传递的是基本数据类型:
其实传递的是值的拷贝。在方法中对值进行操做,并不影响传进去的那个值。如上面的change()方法,传值进去时只是按照data的样子从新建立了一个i,本质上data和i除了值相同之外,是两个独立的个体。

    传递的是数组对象或者其余对象:
    实际上传递的是对象的引用,可是并非把引用传过去,而是把引用复制过去。就像上面的change1()方法同样,其本质是将传参b1这个引用的值复制给引用b。b1和b除了值相同外,是两个独立的个体。可是因为两者值相同,因此指向了堆内存中的同一个对象,两者均可以用来操做对象。

总结一下:
传值,传的都是栈中所储存的东西的拷贝。若是传进去的东西是基本数据类型,那么就直接复制一份,对其操做不影响原来的数据。
若是传进去的是一个引用,那么其实也是复制一份,因此指向同一对象。当操做这个引用时,改变了这个引用所指向的对象,看起来会让人以为当时传进去的是对象自己,否则怎么在方法中对其修改会改变本来的对象呢?其实这是个假象。时刻记住,传进函数的都是栈内存中的东西,堆内存的东西是不会被传进去的。而函数内部能不能改变原来对象的值,就要看你是否是保持了原来传进去的引用所指向的对象没变。

PS:才疏学浅,若有错误请指出,谢谢!  

相关文章
相关标签/搜索