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

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

 class Student{
static String schoolName;
String name;
int age; void speak(String name){
System.out.println(name);
} void read(){
}
}
class Test{
public statis void main(String[] args){
Student a = new Student();
Student b = new Student();
}
}

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

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

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

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

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

  

 public class Test {

     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:才疏学浅,如有错误请指出,谢谢!

Java中类,对象,方法的内存分配的更多相关文章

  1. Java虚拟机垃圾回收:内存分配与回收策略 方法区垃圾回收 以及 JVM垃圾回收的调优方法

    在<Java对象在Java虚拟机中的创建过程>了解到对象创建的内存分配,在<Java内存区域 JVM运行时数据区>中了解到各数据区有些什么特点.以及相关参数的调整,在<J ...

  2. Java虚拟机垃圾收集器与内存分配策略

    Java虚拟机垃圾收集器与内存分配策略 概述 那些内存须要回收,什么时候回收.怎样回收是GC须要完毕的3件事情. 程序计数器.虚拟机栈与本地方法栈这三个区域都是线程私有的,内存的分配与回收都具有确定性 ...

  3. JVM的艺术-对象创建与内存分配机制深度剖析

    JVM的艺术-对象创建与内存分配机制深度剖析 引言 本章将介绍jvm的对象创建与内存分配.彻底带你了解jvm的创建过程以及内存分配的原理和区域,以及包含的内容. 对象的创建 类加载的过程 固定的类加载 ...

  4. .NET的堆和栈03,引用类型对象拷贝以及内存分配

    在" .NET的堆和栈01,基本概念.值类型内存分配"中,了解了"堆"和"栈"的基本概念,以及值类型的内存分配.我们知道:当执行一个方法的时 ...

  5. Java 关于创建String对象过程的内存分配

    一.String s = "abc"  和 String s = new String("abc") 的区别 1.String s = "abc&qu ...

  6. Java的垃圾回收和内存分配策略

    本文是<深入理解Java虚拟机 JVM高级特性与最佳实践>的读书笔记 在介绍Java的垃圾回收方法之前,我们先来了解一下Java虚拟机在执行Java程序的过程中把它管理的内存划分为若干个不 ...

  7. [深入理解Java虚拟机]<垃圾收集器与内存分配策略>

    Overview 垃圾收集考虑三件事: 哪些内存需要回收? 什么时候回收? 如何回收? 重点考虑Java堆中动态分配和回收的内存. Is Object alive? 引用计数法 给对象添加一个引用计数 ...

  8. Java虚拟机--垃圾收集器和内存分配

    垃圾收集器和内存分配 程序计数器.虚拟机栈.本地方法栈这三个区域和线程的生命周期一致,所以方法结束或者线程结束时,内存自然就跟着回收了.Java堆和方法区,只有在程序处于运行期间才能知道会创建哪些对象 ...

  9. 《java虚拟机》----垃圾收集、内存分配

    No1: 程序计数器.虚拟机栈.本地方法栈3个区域随线程而生,随线程而灭:栈中的栈帧随着方法的进入和退出而有条不紊的执行着出栈和入栈操作.每一个栈帧中分配多少内存基本上市在类结构确定下来时就已知的,因 ...

随机推荐

  1. Python的多进程锁的使用

    很多时候,我们需要在多个进程中同时写一个文件,如果不加锁机制,就会导致写文件错乱 这个时候,我们可以使用multiprocessing.Lock() 我一开始是这样使用的: import multip ...

  2. CS231n 2016 通关 第一章-内容介绍

    第一节视频的主要内容: Fei-Fei Li 女神对Computer Vision的整体介绍.包括了发展历史中的重要事件,其中最为重要的是1959年测试猫视觉神经的实验. In 1959 Harvar ...

  3. qq截图原理

    屏幕截图实现的大体思想是:发起截图时,将当前窗口的图像保存到内存中,然后弹出一个置顶的全屏窗口,将保存的桌面图片绘制到这个全屏窗口上:初始时绘制的是灰化的桌面图像,选择截图区域后,则将选中的区域绘制成 ...

  4. asp.net MVC 单选按钮的使用

    单选按钮的标准的html 语法 <form><input type="radio" name="sex" value="male&q ...

  5. Ruby module ---模块,组件

    module 的主要目的是把不同的方法和常量分别放进不同的命名空间. module 的命名方式跟类一样首字母大写,多个单词不用下划线. 如:CircleArea module 语法 module Mo ...

  6. gdb调试带参数的程序 (转载)

    转自:http://www.cnblogs.com/rosesmall/archive/2012/04/10/2440514.html 一般来说GDB主要调试的是C/C++的程序.要调试C/C++的程 ...

  7. 洛谷 - P1198 - 最大数 - 线段树

    https://www.luogu.org/problemnew/show/P1198 要问区间最大值,肯定是要用线段树的,不能用树状数组.(因为没有逆元?但是题目求的是最后一段,可以改成类似前缀和啊 ...

  8. 51nod 1069【思维】

    具体思路来自相关讨论 给个不太严谨的证明思路: 第一步:证明路径可逆,也就是如果(a, b) -> (x, y)可行,则(x, y) - > (a, b)可行 这个比较直观,只需要分别由( ...

  9. bzoj 3594: [Scoi2014]方伯伯的玉米田【二维树状数组+dp】

    设f[i][j]为前i棵玉米被拔高了j(因为是单调不降所以前面越高越好,所以每次拔一个前缀),转移是f[i][j]=f[k][l]+1,l<=j,a[k]+l<=a[i]+j,然后用二维树 ...

  10. P1228-重叠的图像

    一道很水的topsort,唉?怎么交了14遍...(某人用我的代码刚好卡过,我怎么过不去...[鄙视][鄙视][鄙视]) #include <bits/stdc++.h> using na ...