首先必须说明作为Java程序员对于内存只要有大致的了解就可以了,如果你对Java当中的某一个知识点在不需要分析内存分配过程的情况下可以掌握,那就大可不必去研究内存。如果你对知识点已经掌握,那么你应该把更多的精力放在对业务逻辑的分析与设计上,这样的话你才可能这一行业走的更远。
好了废话不多说了,下面我带着大家先来简单的看一下Java当中所涉及的内存分配,接着我会以讲解Java当中的值传递问题,分析在代码执行的过程当中内存的状态。
一、Java当中所涉及到的内存分类
Java当中你知道这5种内存就够用了,下面对这5种内存里面所存放的数据做一解释。
① 栈内存:它里面存放的是引用(也就是地址,Java当中的这个地址并非内存的物理地址,但是它通过这个地址找到它所指向地址的内容)还有就是基本类型的值以及方法的形参也是存放在栈内存当中的。
② 对内存:它里面存放的是对象、引用、基本类型的值(对于引用和基本类型的值什么时候放在栈里什么时候放在堆里,在后面讲解Java当中的值传递问题,分析在代码执行的过程当中内存的状态的时候会说)。
③ 寄存器:它里面存放的是中间运算的数字(对于这个我们可以忽略不去考虑它)。
④ 代码段:顾名思义它里面放的就是程序的代码。
⑤ 池内存:池里面方的是常驻内存反复利用的数据。
好了这就是Java当中常见内存以及它里面所存放的数据,下面我们通过讲解Java当中的值传递问题,分析在代码执行的过程当中内存的状态。
二、Java当中的值传递问题以及代码执行过程当中内存的状态
什么是值传递?
值传递就是Java当中参数传递的一种方式(而且也是唯一的一种方式,也就是说Java当中只有值传递),所谓参数传递就是在某个方法被调用的时候把一个实参传递给形参的过程。
下面我们通过分析下面代码执行过称中内存的状态来说明Java当中的参数传递以及为什么Java当中只有值传递。
代码清单:(为了节省空间格式不是很规范)
定义学生类:
定义测试类:
测试结果:
为什么会有这样的结果?下面我们分析一下这段代码执行过称当中内存的分配,相信问题将迎刃而解。
1、 我们运行TestPassing这类,虚拟机加载TestPassing这个类,虚拟机将这些代码存放到代码段当中(这里我们就不画出代码段的图示了,后面虚拟机调用任何方法(包括构造方法)都要先到代码段中去找,但是这比较简单也不是重点接下来的解析当中如果涉及到方法调用就不再说明了),然后虚拟机从代码段当中找到main()方法,开始执行代码。
此时虚拟机为main()创建栈内存,内存分配如下
2、 接着执行int age = 20;这行代码,由于它是基本类型的局部变量所以直接把它的值20存在栈内存名字叫age,内存分配如下
3、 接着执行TestPassing tp = new TestPassing();这一行代码,这句话在内存当中做了3个操作,首先TestPassing tp,tp是一个引用类型的变量所以给它分配一块栈内存存放一个TestPassing对象的引用(也就是地址假设这个地址是ox 1a2b3c),接下来在堆内存创建一个TestPassing对象,接着把刚才栈里面tp的引用指向堆里面的这个TestPassing对象,这行代码的顺序之所以是这样是因为“=”的优先级比“new”的优先级低。内存分配如下(在此只给出最终内存分配图)
4、 接着执行这一行代码tp.addAge(age); TestPassing对象调用addAge(int age)方法,虚拟机为addAge(int age)方法分配一个临时的栈内存,并且在这块临时栈内存当中为addAge(int age)的形参age也分配一小块栈内存,接着把main()当中的实参age的副本(注意是实参age的副本而不是实参age)传给形参age,由于实参age是基本类型所以实参age的副本就是20,也就是说把20传给形参age,此时的内存分配如下
这行代码到此还没有执行完,参数传过去之后接着程序跳到被调方法当中去执行,也就是执行age++;此时操作的是形参age与实参age没有任何关系,age++;完了之后形参age的值变成21,此时的内存分配如下
被调方法还没结束,程序接着往下执行到方法体的结束大括号,被调方法执行完毕,同时addAge(int age)的临时栈内存关闭。此时的内存分配如下
5、 程序接着执行System.out.println("age=" + age);(这一行代码的内存分配过程我想没人想让我画吧)这一行代码,很清楚看上面的内存图,也就不难理解为什么打印出20了。
6、 接下来程序执行到Student s1 = new Student();这一行代码和上面TestPassing tp = newTestPassing();内存分配的过称基本一样,这句话也是在内存当中做了3个操作,首先Student s1,s1是一个引用类型的变量所以给它分配一块栈内存存放一个Student对象的引用(假设这个地址是ox1a2b3d),接下来在堆内存创建一个Student对象,它有一个int类型属性age,所以在刚才创建的对象的大块内存当中分出一小块来存放这个属性,里面存的值是0名在叫age(全局变量有默认值所以我们没给它赋值就默认为0),接着把刚才栈里面s1的引用指向堆里面的这个Student对象。此时的内存分配如下
7、 接着程序执行s1.age = 20;这一行代码,这行代码将堆内存当中的Student对象的age改为20,此时的内存分配如下
8、 接着程序执行tp.addAge(s1);这一行代码,TestPassing对象调用addAge(Student s)方法,虚拟机为addAge(Student s)方法分配一个临时的栈内存,并且在这块临时栈内存当中为addAge(Student s)的形参s也分配一小块栈内存,接着把main()当中的实参s1的副本传给形参s,但是s1是引用类型它的副本就是它现在在栈内存里面的地址,也就是说把Student的地址传给形参s,所以形参就会根据这个地址找到Student对象,此时的内存分配如下
这行代码到此还没有执行完,参数传过去之后接着程序跳到被调方法当中去执行,也就是执行s.age++;此时它操作的时是真正的Student对象,所以这行代码执行完了之后Student对象的age属性就变成了21,此时的内存分配如下
被调方法还没结束,程序接着往下执行到方法体的结束大括号,被调方法执行完毕,同时addAge(Student s)的临时栈内存关闭。此时的内存分配如下
9、 程序接着执行System.out.println("s1.age=" + s1.age);这一行代码,很清楚看上面的内存图,也就不难理解为什么打印出21了。
10、 main()结束,main()栈内存关闭,没有任何引用指向堆内存当中的TestPassing对象和Student对象,垃圾回收器回收资源,虚拟机关闭。
好了关于Java当中的内存分配以及值传递问题内存解析就说到这,Java当中的池内存也是一个很重要的概念,由于时间关系本次分析并未提及池内存,有时间再给大家分享。可以给大家一个思考题,如果给addAge(int age) 这个方法再加一String类型的形参也就是把这个方法改成addAge(int age, String name)并在这个方法里面改变name的值,给Student类再加一个属性String name,并在addAge(Student s)方法当中修改s.name的值,这样的话String是引用类型,那么name会怎样变呢?