前文知道了java程序运行时在内存中的大概分布,但是对于具体程序是如何运行的,看到一篇文章,直接转载过来。

(一)不含静态变量的java程序运行时内存变化过程分析

代码:

 package oop;

 /**
* 说明:实体类
*
* @author huayu
* @date 2018/8/3
*/
public class Birthday {
private int day;
private int month;
private int year;
//有参构造方法
public Birthday(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;
} @Override
public String toString() {
return "Birthday{" +
"day=" + day +
", month=" + month +
", year=" + year +
'}';
}
} import oop.Birthday; /**
* 说明:测试类
*
* @author huayu
* @date 2018/8/3
*/
public class TestBirthday {
public static void main(String[] args) {
//new TestBirthday();相当于调用了TestBirthday类的无参构造方法(编译器默认加的)
1. TestBirthday test=new TestBirthday();
//调到这句在栈内存分配一块名为date,值为9的内存
2. int date=9;
//new Birthday(7,7,1970);相当于调用了Birthday类中的有参构造方法
3. Birthday d1=new Birthday(7,7,1970);
4. Birthday d2=new Birthday(1,1,2000);
5. test.change1(date);
6. test.change2(d1);
7. test.change3(d2);
8. System.out.println("date="+date);
9. d1.toString();
10. d2.toString(); }
public void change1(int i){
11. i=1234;
}
public void change2(Birthday b){
12. b=new Birthday(22,2,2004);
}
public void change3(Birthday b){
13. b.setDay(22);
}
}

内存过程分析:

在做分析以前我们应该预备的知识有:

1)栈内存储的是局部变量,基础类型的局部变量也分配在栈中,而且它只占一块内存:如下图栈中的局部变量date,一个int类型变量分配了一块int类型空间,四个字节,里面装了个值9,名字叫做date。

2)引用类型在内存中占两大块(栈中跟堆中),一块在栈中存的是指向对象的引用(对象在堆中的地址),一块在堆中存的是new出来的对象。(凡是new出东西来,则在堆内存中做分配):如下图栈中的局部变量test,一个引用类型分配了一块内存用于存test的引用(即对象在堆中的存储地址,后期利用这个存储地址去找到并操作堆中new出来这个引用的对象),它指向了在堆中new出来的对象的一块内存(即图中那块未存东西的空位置)。

3)方法中的形参与局部变量等同

4)方法调用的时候是值传递

5)方法执行完毕后,在栈中为这个方法分配的局部变量的空间全部消失。

6)在堆中new出来的对象若栈中没有了它的引用(也就是不用它了),后期会被GC清理掉那部分堆中的内存,而GC回收的具体时间不可控。

上面代码中当执行到TestBirthday中main方法第四句时,内存里面的布局是这个样子的(下图1中所示堆中的d1对象显示的好像是3块,其实就是一个块,也就是堆中就一个d1对象,我为了看起来清楚才这么画的自己知道就可以了,d2同理也是就一个对象)。

(1)执行change1(int i)方法

接下来我们开始执行第五句chage1(int i)方法,而里面有个形参i,当我们调用这个方法的时候,首先应该在栈里面为形参(与局部变量等同)分配一块空间,如下分配了一个空间名字叫i,值的话是实参传了多少就是多少,在这实参是date且值为9,所以形参i的值是9(方法调用的时候是值传递,相当于把date的值9复制给了i),此时的内存布局为图2:

执行到第11句,i=1234;到这时i的值由9变为了1234,但是date值不会变,因为i当时的值9是date复制了一份给它的,所以i的改变对date无影响。此时的内存布局为图3:

当执行完change1方法后,形参i在栈中分配的空间被收回(注意这时date依然不受影响哈),此时的内存布局为图4

(2)执行change2(Birthday b)方法

首先,形参要求传一个Birthday  b的引用类型,所以我们将d1传进来。Birthday  b是一个局部变量,二话不说在栈内存空间内分配一个变量b,b中装的是实参中传的内容(即d1中的内容),当执行了这个方法之后,它会把d1中的值复制给b。此时b中的地址跟d1是一样的,他们指向的是同一个对象(堆中的d1对象)。此时的内存布局为图5:

当执行第12句b=new Birthday(22,2,2004);这一句时,堆中这是又会new出一个新的对象(凡是new出东西来,则在堆内存中做分配),此时栈中的引用地址指向新new出来的b的对象(注意到栈中b的引用地址也变化了)。此时的内存布局为图6:

当方法change2执行完毕后,为其在栈中分布的局部变量空间也随之消失。注意,此时栈中为方法执行分布的局部变量被收回了,但是它在堆上new出来的对象b不一定消失,它会等待GC来回收它,具体回收时间不可控,所以我们也不确定它什么时候能消失,但是弱栈中没有了指向它的引用,这个b对象迟早会被GC清理掉,早晚会消失。此时的内存布局为图7:

(3)执行change3(Birthday b)方法

首先,形参要求传一个Birthday  b的引用类型,所以我们将d2传进来。Birthday  b是一个局部变量,二话不说在栈内存空间内分配一个变量b,b中装的是实参中传的内容(即d2中的内容),当执行了这个方法之后,它会把d2中的值复制给b。此时b中的地址跟d2是一样的,他们指向的是同一个对象(堆中的d2对象)。此时的内存布局为图8:

当执行到第13句b.setDay(22);(是Birthday类中的public void setDay(int day) {this.day = day;}这个方法),它会利用b这个引用去操作d2对象,当传入实参22给int day后它会把值传给this.day,则day的值真正被改变了(注意,此时的栈中b引用跟d2引用的值都没变,还是指向同一个对象)。此时的内存布局为图9:

当方法change3执行完毕后,为其在栈中分布的局部变量空间也随之消失。注意,虽然此时栈中b引用虽然消失了,但它指向的对象d2还有栈中的d2引用在,所以堆中对象d2不会消失。此时的内存布局为图10:

change1,change2方法调用完后,或早或晚的栈内存,堆内存中分配的空间的都会被回收,没起任何实质性作用,相当于白调了。而change3方法调用了实体类Birthday中的具体方法,确实并对堆内存中仍然被栈空间的引用d2所引用的堆内对象做了修改产生了实质性作用。

(二)含static静态变量的java程序运行时内存变化过程分析

预备知识:static关键字

1)在类中,用static声明的成员变量为静态成员变量,它为该类的公用变量。在第一次使用时候被初始化,对于该类的所有对象来说,static成员变量只有一份。

2)用static声明的方法为静态方法,在调用该方法时,不会将对象的引用传递给它,所以在static方法中不可访问非static的成员。(静态方法只能访问静态成员,因为非静态方法的调用要先创建对象,在调用静态方法时可能对象并没有被初始化)。

3)可以通过对象引用或类名(不需要实例化)访问静态成员。

4)static静态变量存在在data seg数据区,就算是不new它,它也会在data seg部分保留一份。静态变量是属于整个类的,它不属于某一个对象。

知识链接:字符串常量在内存中分配也是在data segment部分。

 /**
* 说明:静态变量内存分析举例
*
* @author huayu
* @date 2018/8/3
*/
public class Cat {
//静态成员变量,就算不new对象它也会在data seg里面保存一份,它属于整个类
//不属于某个对象。int静态变量可以用来计数。
//对静态值访问:1.任何一个对象都可以访问这个静态对象,访问的时候都是同一块内存
//2.即便是没有对象,也可以通过 类名. 来访问 如:System.out out是个静态变量
1. private static int sid=0;
//非静态成员变量 new对象的时候在堆内存对象中保存,每new一个对象产生一块
2. private String name;
//非静态成员变量 new对象的时候在堆内存对象中保存,每new一个对象产生一块
3. private int id; public Cat(String name) {
4. this.name = name;
5. id=sid++;
} public void info(){
6. System.out.println("My name is "+name+" No."+id);
} public static void main(String[] args) {
//静态变量sid属于整个Cat类,不属于某个对象,可以用类名.来访问,所以这儿没有new任何对
//象,直接用类名.(Cat.sid)来访问的。
7. Cat.sid=100;
//字符串常量分配在data seg
8. Cat mimi=new Cat("mimi");
9. Cat pipi=new Cat("pipi");
10. mimi.info();
11. pipi.info(); }
}
My name is mimi No.id=100 sid= 102
My name is pipi No.id=101 sid= 102

执行完7Cat.sid时,静态变量sid值为100。内存分布状态如下:

(1)执行第7句构造方法

第一步:执行第7句Cat mimi=new Cat("mimi");字符串常量“mimi”分布在data segment部分,内存分布如下(这儿看不懂的再从头看这篇文章):

第二步:调到上面后就该到Cat的构造方法了,执行第4句this.name = name;这时根据传入构造方法的name形参,栈中就会为其开辟一块名为name的空间,实参“mimi”传了进来,这时候栈中name的引用指向了data segment中的字符串常量“mimi”,因为this.name = name,所以自身成员变量的this.name也指向了data segment中的字符串常量“mimi”。

第三步:执行id=sid++;mimi对象的成员变量i值为原来sid的值100,接下来sid++,将sid的值改为101,内存状态如下图:

第四步:构造方法执行完成后,为执行这个方法在栈中分配的形参变量的内存空间收回,name在栈中的空间消失(当然,为执行方法而在栈中分配的局部变量空间,方法执行完毕后都应该被收回了)

(2)执行Cat pipi=new Cat("pipi"); 方法跟执行上面那个构造方法原理是一样的(当然,为执行方法而在栈中分配的局部变量空间,方法执行完毕后都应该被收回了),大家自己画一下,我这边把最后的内存分布状态给一下大家:

原文详见:https://blog.csdn.net/Myuhua/article/details/81385609

转载:java程序调用内存的变化过程的更多相关文章

  1. (转)java程序调用内存变化过程分析(详细)

    原博地址: https://blog.csdn.net/Myuhua/article/details/81385609 (一)不含静态变量的java程序运行时内存变化过程分析 代码: package ...

  2. Java程序编译和运行的过程

    Java整个编译以及运行的过程相当繁琐,本文通过一个简单的程序来简单的说明整个流程. 如下图,Java程序从源文件创建到程序运行要经过两大步骤:1.源文件由编译器编译成字节码(ByteCode)  2 ...

  3. java程序的内存分配

    java程序的内存分配 JAVA 文件编译执行与虚拟机(JVM)介绍 Java 虚拟机(JVM)是可运行Java代码的假想计算机.只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的 ...

  4. java程序的内存分配(一)

      首 页 阅览室 馆友 我的图书馆 帐号 java程序的内存分配(一) 收藏  JAVA 文件编译执行与虚拟机(JVM)介绍  Java 虚拟机(JVM)是可运行Java代码的假想计算机.只要根据J ...

  5. Java程序在内存中运行详解

    目录 Java程序在内存中运行详解 一.JVM的内存分布 二.程序执行的过程 三.只有一个对象时的内存图 四.两个对象使用同一个方法的内存图 五.两个引用指向同一个对象的内存图 六.使用对象类型作为方 ...

  6. 通过java程序调用ant build.xml配置文件中指定的target

    一.概述 通过ant实现项目的自动化部署,jar包生成,替换,tomcat关停.启动,查看项目日志: 通过java程序调用已编辑好的ant脚本build.xml配置文件中指定的target: 文中文件 ...

  7. java程序调用存储过程

    java程序调用存储过程       PL/SQL子程序,很多情况下是给应用程序来调用的,所有我们要掌握使用其他编程语言来调用我们写好的存储过程.下面我们介绍下使用java调用Oracle的存储过程. ...

  8. java程序调用存储过程和存储函数

    java程序调用存储过程 jdbcUtil.java文件 package cn.itcast.oracle.utils; import java.sql.Connection; import java ...

  9. 【原】Java程序调用远程Shell脚本

    此程序的目的是执行远程机器上的Shell脚本. [环境参数]远程机器IP:192.168.234.123用户名:root密码:rootShell脚本的路径:/home/IFileGenTool/Bak ...

随机推荐

  1. jquery ajax实例教程和一些高级用法

    jquery ajax的调用方式:jquery.ajax(url,[settings]),jquery ajax常用参数:红色标记参数几乎每个ajax请求都会用到这几个参数,本文将介绍更多jquery ...

  2. 学号20155311 2016-2017-2 《Java程序设计》第4周学习总结

    教材学习内容总结 6.1 何谓继承 何谓继承 面向对象中,子类继承父类,避免重复的行为定义,不过并非为了避免重复定义行为就使用继承,滥用继承而导致程序维护上的问题时有所闻.如何正确判断使用继承的时机, ...

  3. 20145226夏艺华 Exp6 信息搜集与漏洞扫描

    20145226夏艺华 Exp6 信息搜集与漏洞扫描 基础问题回答 哪些组织负责DNS,IP的管理? · 全球根服务器均由美国政府授权的ICANN统一管理,负责全球的域名根服务器.DNS和IP地址管理 ...

  4. 【bzoj3991】[SDOI2015]寻宝游戏 树链的并+STL-set

    题目描述 给出一棵树,初始每个点都是非必经的.多次改变某个点的必经状态,并询问从任意一个点出发,经过所有必经的点并回到该点的最小路程. 输入 第一行,两个整数N.M,其中M为宝物的变动次数. 接下来的 ...

  5. 向日期添加指定的时间间隔(mysql)

    DATE_ADD( 原始日期, INTERVAL 要加的年数 YEAR) DATE_ADD( 原始日期, INTERVAL 要加的月份 MONTH) DATE_ADD( 原始日期, INTERVAL ...

  6. IIS解决上传文件大小限制

    目的:通过配置文件和IIS来解决服务器对上传文件大小的限制 1:修改配置文件(默认为4M 值的大小根据自己情况进行修改) <httpRuntime  maxRequestLength=" ...

  7. Python小白学习之函数装饰器

    装饰器 2018-10-25 13:49:37 装饰器从字面意思就是用来装饰的,在函数可以理解为:在函数中,我们不想影响原来的函数功能,又想给函数添加新的功能,这时候我们就用到了装饰器. 一般函数操作 ...

  8. 求两个字符串的最长公共子串——Java实现

    要求:求两个字符串的最长公共子串,如“abcdefg”和“adefgwgeweg”的最长公共子串为“defg”(子串必须是连续的) public class Main03{ // 求解两个字符号的最长 ...

  9. 解决Sublime Text 3中文显示乱码(tab中文方块)问题

    博客分类:  Sublime   一.文本出现中文乱码问题 1.打开Sublime Text 3,按Ctrl+-打开控制行,复制粘贴以下python代码,然后回车运行. 2. 复制并粘贴如下代码: P ...

  10. ideal快捷键

    百度一搜索,发现很多快捷键说明,我但是有些说得不对的,我列出来的这些快捷键,有一部分是需要你百度好久,甚至百度一上午才能搜索出来的,并且戴着老花镜.这样的话,在实际工作者,对于初级程序员来说,成本太高 ...