学习内存图+6个例子(一)

目录

有两篇文章学习内存图
  • 学习内存图+6个例子(一)
  • 学习数组内存图(二)
  • 定义变量

    基本类型:

    8大类型:
    类型根据开辟空间分类占比特(bit)位
    整数byte8
    整数short16
    整数int32
    整数long64
    浮点数float32
    浮点数double64
    布尔boolean
    字符char

    基本类型用=就直接开辟了空间:
    例如:

    int num1 = 10;
    float num2 = 50.2f;
    

    引用类型:

    除了八个是基本类型,剩下的都是引用类型。如:类、接口、数组
    绝大对数都用new开辟空间。
    Java提供的ArrayList类,是实现动态数组:把引用的类型以参数的形式传过去

    ArrayList list = new ArrayList<>();  //<>是泛型
    //例如:
    ArrayList<String> list = new ArrayList<String>();
    
    特例:

    字符串两种方式:

    String str1 = new String("hello");
    String str2 = ("hello");
    

    数组三种方式:

    int[] arr1 = new int[5];
    int[] arr2 = new int[]{1,5,6,8};
    int[] arr2 = {1,5,6,8};
    

    内存图

    内存图可大致分为:

    1. 方法区:又细分为类常量池静态常量池
    2. 栈区:控制哪个线程栈执行,方法的入栈出栈,控制方法的执行;有线程栈多线程栈互不打扰。栈的空间小。
    3. 堆区:引用类型数据开辟空间。字符串常量池存储string赋值的数据。堆的空间大。
    4. 程序计数器:控制当前那个方法在调用,执行到了什么状态,变量什么时候有效什么时候无效。
    5. 本地方法栈
      图示:
      在这里插入图片描述

    例1(画图入门):

    代码

    建一个Test1类:

    package com.neicuntu;
    
    public class Test1 {
    	//属性
    	static int xx = 20;
    	
    	//方法
    	public static void main(String[] args) {
    		//定义变量
    		//基本类型
    		int num1 = 10;
    		float num2 = 50.2f;
    		
    		//引用类型
    		Cat cat1 = new Cat();
    		Cat cat2 = new Cat();
    		
    		//字符串
    		String str1 = new String("hello");
    		String str2 = "hello2";
    		
    		//数组
    		int[] arr1 = new int[5];
    		int[] arr2 = new int[] {1,4,7,6};
    		int[] arr3 = {1,4,7,6,10};
    	}
    }
    

    建一个Cat类:

    package com.neicuntu;
    
    public class Cat {
    	
    }
    

    内存图过程:

    文字解释:

    1. 类常量池中有Test类和Cat类,Test中有xx属性和main方法,均被static修饰(被修饰的会进行背景加颜色),Cat类中没有任何方法和属性;
    2. static修饰的,加载进内存之后,会立马到静态常量池里开辟空间;所以静态常量池中开辟Test类中的xx属性的32比特空间=20,和main方法空间;main方法里的东西只有调用的时候执行;
    3. 在线程栈中执行:线程栈只看栈顶是谁,最上面是谁就执行谁
      • main方法入栈;
      • 基本类型数据直接在栈区中开辟空间:开辟num1的32比特空间,存10;开辟num2的32比特空间,存50.2
      • 引用类型数据:在堆区中开辟一个空间,其地址为0x11,cat1指向0x11;同理,堆区中开辟一个空间,其地址为0x22,cat2指向0x22;
      • new出来的差不多是这种模板,可得出arr1、arr2、arr3均是指向堆区中的开辟出的空间地址,如图中所示;
      • 但有一个特殊类型:字符串String类型,str2是直接=出来的数据,其单独的在堆区中有一个空间----字符串常量池中开辟空间;而str1在堆区开辟一个空间用来存储在字符串常量池中存储数据的地址,str1指向堆区的这个地址;
      • arr3之后,发现一个},整个方法结束,方法main出栈,所有开辟的变量 (跟着方法走的变量叫局部变量) 一起出栈,堆区中的空间没有指向了,会被回收。

    图示:
    入门

    由此可得出规则

    1. static修饰的属性或方法可直接在静态常量池中开辟空间;
    2. 方法中的基本类型的可直接在栈区开辟空间;
    3. 方法中的引用类型使用new修饰的基本在堆区开辟空间,然后栈区指向堆区的地址;
    4. 引用类型中特殊的String:如果是String str2 = "hello2";写法,则是在堆区中的字符串常量池中开辟空间;如果是String str2 = new String("hello2");写法,在堆区中的字符串常量池中开辟一个空间存储数据,在堆区中开辟一个空间存储字符串常量池中的地址;
    5. 栈区中的方法执行到},整个方法结束,栈区中的方法出栈,方法中的变量跟随一起出栈;
    6. 由于栈区中的方法、变量出栈,堆区中的空间没有指向,其空间会被回收;
    7. 特殊:字符串常量池中开辟空间不会因为没有指向就被回收,而是等池子满了,才会清掉没有指向的空间。

    例2(static修饰):

    代码

    建一个Test类:

    package com.neicuntu;
    
    public class Test {
    	//属性
    	public static int xx = 20; //静态属性
    	public int yy = 200;       //普通属性
    	public String zz = new String("hello");//普通属性
    	
    	//静态方法
    	public static void main(String[] args) {
    		//被`static`修饰的都是静态属性、静态方法,都是直接访问
    		System.out.println(xx);
    		change();
    		
    		//没有被`static`修饰的为普通属性、普通方法,需要用`new`对象
    		Test test = new Test();
    		System.out.println(test.yy);
    		System.out.println(test.zz);
    		test.eat();
    	}
    	
    	//静态方法
    	public static void change() {
    		System.out.println("change方法执行啦");
    	}
    	
    	//普通方法
    	public void eat() {
    		System.out.println("eat方法执行啦");
    	}
    }
    

    记点:

    - 被`static`修饰的都是静态属性、静态方法,都是直接访问
    - 没有被`static`修饰的为普通属性、普通方法,需要用`new`对象
    - 所有被`static`修饰的,在内存图中都进入静态常量池中,已经被分好空间
    

    打印结果:

    20
    change方法执行啦
    200
    hello
    eat方法执行啦
    

    内存图过程:

    文字解释:

    1. 类常量池中有Test类:xx属性、yy属性、zz属性、main方法、change方法、eat方法(背景有颜色的是有static修饰,背景为白色的没有被修饰);
    2. xx属性在静态常量池中开辟32比特的空间,存20,打印20;开辟main方法和change方法的空间;
    3. 调用:
      • main方法入栈;
      • change方法入栈;
      • 在栈区,change在上,先执行change方法;打印:change方法执行啦,然后change方法出栈;
        入栈
      • 执行main方法:main中有test变量,在堆区开辟test空间,其地址为0x11,栈区指向该地址;
      • 堆区中的test空间中:yy直接开辟32比特空间,存200,打印200;zz字符串指向堆开辟空间存储字符串常量池中存储数据的地址的地址,打印hello;开辟eat方法空间,打印eat方法执行啦;
      • 最后main方法执行完,main方法出栈。

    图示:
    例2完整图示

    例3(内容修改):

    代码

    在例2的代码基础上进行添加代码:

    package com.neicuntu;
    
    public class Test {
    	public static int xx = 20; //静态属性
    	public int yy = 200;       //普通属性
    	public String zz = new String("hello");//普通属性
    	
    	//静态方法
    	public static void main(String[] args) {
    		//new一个变量test
    		Test test = new Test();
    		System.out.println(test.xx);
    		System.out.println(test.yy);
    		System.out.println(test.zz);
    
    		System.out.println("--------------------------");
    
    		//new一个变量test1
    		Test test1 = new Test();
    		System.out.println(test1.xx);
    		System.out.println(test1.yy);
    		System.out.println(test1.zz);
    		
    		//修改内容
    		test1.xx = 1111;
    		test1.yy = 222;
    		test1.zz = new String("demo");
    		
    		System.out.println("===========================");
    		System.out.println(test.xx);
    		System.out.println(test.yy);
    		System.out.println(test.zz);
    		System.out.println(test1.xx);
    		System.out.println(test1.yy);
    		System.out.println(test1.zz);
    	}
    	
    	//静态方法
    	public static void change() {
    		System.out.println("change方法执行啦");
    	}
    	
    	//普通方法
    	public void eat() {
    		System.out.println("eat方法执行啦");
    	}
    }
    

    打印结果:

    20
    200
    hello    //这是test打印的结果
    --------------------------
    20
    200
    hello    //这是test1打印的结果
    ===========================
    1111
    200
    hello    //这是通过test1修改xx打印的结果
    1111
    222
    demo    //这是通过test2修改yy和zz打印的结果
    

    内存图过程:

    文字解释:

    1. 开辟test之前的过程与例2过程一样;
    2. 在堆区中开辟test1的空间,其地址为0x33,栈区test1指向该地址;
      test1开辟空间
    3. 开始改值:
      • 通过test1中找xx,改值为1111;
      • 通过test1中找yy,改值为222;
      • 通过test1中找zz,zz又new了一个新的值,在字符串常量池中新开盘一个空间,存储demo,其地址为0x55,堆区空间0x44指向0x55,test1中的zz指向0x44。
        修改内容

    图示:
    例3完整图示

    例4(跨类调用):

    代码

    建一个Cat类的文件:

    package com.neicuntu;
    
    public class Cat {
    	public static int count = 10; 
    	public static String country = "中国"; 
    	public String name = new String("猫猫");
    	public int age = 2;
    	
    	public static void run() {
    		System.out.println("跑跑跑");
    	}
    	
    	public void eat() {
    		System.out.println("吃吃吃");
    	}
    }
    

    建一个Test类的文件:

    package com.neicuntu;
    
    public class Test{
    	public static void main(String[] args) {
    		//跨类调用
    		//静态方法调用
    		Cat.run();
    		System.out.println(Cat.count);
    		
    		//非静态方法调用
    		Cat cat1 = new Cat();
    		cat1.eat();
    		System.out.println(cat1.count);
    		System.out.println(cat1.country);
    		System.out.println(cat1.name);
    		System.out.println(cat1.age);
    		
    		Cat cat2 = new Cat();
    		cat2.eat();
    		System.out.println(cat2.count);
    		System.out.println(cat2.country);
    		System.out.println(cat2.name);
    		System.out.println(cat2.age);
    		
    		//修改值
    		cat2.count = 999;
    		cat2.country = "小家";
    		cat2.name = "豆豆";
    		cat2.age = 5;
    		
    		//打印
    		System.out.println(cat1.count);
    		System.out.println(cat1.country);
    		System.out.println(cat1.name);
    		System.out.println(cat1.age);
    		System.out.println(cat2.count);
    		System.out.println(cat2.country);
    		System.out.println(cat2.name);
    		System.out.println(cat2.age);
    	}
    }
    

    打印结果:

    跑跑跑
    10           //打印静态方法调用的结果
    ---------------------------------------------------------
    吃吃吃
    10
    中国
    猫猫
    2           //打印非静态方法调用,new cat1的结果
    ---------------------------------------------------------
    吃吃吃
    10
    中国
    猫猫
    2           //打印非静态方法调用,new cat2的结果
    ---------------------------------------------------------
    999
    小家
    猫猫
    2           //修改count值、country值、name值、age值,打印new cat1的结果
    ---------------------------------------------------------
    999
    小家
    豆豆
    5           //修改count值、country值、name值、age值,打印new cat2的结果
    

    内存图过程:

    文字解释:

    1. 类常量池中有:
      • Cat类:属性:count(静态)、country(静态)、name、age;方法:run()(静态)、eat()
      • Test类:方法:main()(静态)
    2. 方法解析好后,挂着静态的资源都会拷贝复制一份进入静态常量池里:
    • Cat类:属性:count(静态)、country(静态);方法:run()(静态)
    • Test类:方法:main()(静态)
      方法区
    1. 方法入栈出栈:
      • Test main()方法入栈,main()方法里的语句一句一句的执行;
      • 执行Cat.run():Cat run()入栈,执行,打印跑跑跑,碰到},方法结束,出栈;
        方法入栈
      • 执行打印Cat.count语句,打印10;
      • 执行Cat cat1 = new Cat();相关的语句:
        • 在栈区中的Test main()方法中开辟cat1的空间;
        • 在堆区中开辟cat1中内容的空间,静态的不要,只要非静态的,其地址为0x11;
          cat入栈开辟空间
      • 执行Cat cat2 = new Cat();相关的语句,与cat1同理:
        cat1入栈开辟空间
      • 执行修改值的语句:
        • 通过cat2修改count的值,修改为999;
        • 通过cat2修改country的值,修改为小家;
        • 通过cat2修改name的值,修改为豆豆;
        • 通过cat2修改age的值,修改为5;
          修改值
      • 执行打印结果的语句,然后碰到},Test main()方法结束,Test main()出栈。

    图示:
    例4完整图示

    例5(数值无效交换):

    代码

    建一个Cat类的文件:

    package com.neicuntu;
    
    public class Cat {
    	public static int count = 10; 
    	public String name = 猫猫";
    	public int age = 2;
    	
    	public static void run() {
    		System.out.println("跑跑跑");
    	}
    	
    	public void eat() {
    		System.out.println("吃吃吃");
    	}
    }
    

    建一个Test类的文件:

    package com.neicuntu;
    
    public class Test{
    	public static void main(String[] args) {
    		//基本类型的交换
    		int num1 = 10;
    		int num2 = 20;
    		System.out.println(num1);
    		System.out.println(num2);
    		change1(num1, num2);
    		System.out.println(num1);
    		System.out.println(num2);
    		
    		System.out.println("==============");
    		
    		//引用类型的交换
    		String str1 = "hello";
    		String str2 = "hi";
    		System.out.println(str1);
    		System.out.println(str2);
    		change2(str1, str2);
    		System.out.println(str1);
    		System.out.println(str2);
    		
    		System.out.println("==============");
    		
    		//自定义的交换
    		Cat cat1 = new Cat();
    		Cat cat2 = new Cat();
    		System.out.println(cat1);
    		System.out.println(cat2);
    		change3(cat1, cat2);
    		System.out.println(cat1);
    		System.out.println(cat2);
    		
    	}
    	public static void change1(int a,int b) {
    		int temp = a;
    		a = b;
    		b = temp;
    	}
    	public static void change2(String a,String b) {
    		String temp = a;
    		a = b;
    		b = temp;
    	}
    	public static void change3(Cat a,Cat b) {
    		Cat temp = a;
    		a = b;
    		b = temp;
    	}
    }
    

    打印结果:

    10
    20   //交换前打印的结果
    10
    20   //交换后打印的结果
    ==============
    hello
    hi   //交换前打印的结果
    hello
    hi   //交换后打印的结果
    ==============
    com.zcm.neicuntu.Cat@7de26db8
    com.zcm.neicuntu.Cat@1175e2db   //交换前打印的结果
    com.zcm.neicuntu.Cat@7de26db8
    com.zcm.neicuntu.Cat@1175e2db   //交换后打印的结果
    

    从打印的结果看,交换前后的结果没变化。

    内存图过程:

    文字解释:

    1. Cat类和Test类中的属性、方法进入方法区;
    2. Test main方法入栈:
      • 执行开辟num1和num2的32比特空间,分别存储10、20,然后打印;
      • 调change1方法:
        • change1方法入栈,先执行change1方法里的内容;
        • 开辟a和b的空间,change1中进行的是值传递:num1的值拷贝一份传给a,a=10,num2的值拷贝一份传给b,b=20;
        • temp=a:开辟temp的空间为10;a=b:a的值为20;b=temp:b的值为10;
        • 此时,在change方法中a和b的值是交换了的;
        • 但是change1方法中执行遇到},change1方法整体结束,change1方法出栈,change1方法里变量跟随一起出栈;
        • 因此,change1方法里的改变不会影响main方法里的num1和num2的值的改变,change1方法里的交换失败。
          入方法区和栈区
      • 开辟str1和str2的空间:在字符串常量池中开辟存储hello的数据和hi的数据,地址分别为0x11、0x22,str1指向0x11,str2指向0x22;
      • 调change2方法:
        • change2方法入栈,先执行change2方法里的内容;
        • 过程与change1中的过程一样,是将str的只进行传递,a=0x22,b=0x11,temp=0x11;
        • 执行遇到},change2方法整体结束,change2方法出栈,change2方法里变量跟随一起出栈;
        • 因此,change2方法里的改变不会影响main方法里的str1和str2的值的改变,change2方法里的交换失败。
          调change2方法
      • 在堆区中开辟cat1和cat2的空间;
      • 调change3方法:
        • 过程与change1和change2过程一样
          调change3方法

    图示:
    li5完整图示

    为什么cat打印结果是地址呢?

    打印默认调用的都是toString()方法,无论写没写,均调用该方法。
    字符串调用:String - String

    • 前面String表示方法返回类型,返回的是一个String类型的数据;
    • 后面String表示谁提供的这个方法,是String类提供的方法。
      String - String

    String - Object:

    • 前面String表示方法返回类型,返回的是一个String类型的数据;
    • 后面Object表示有Object类提供的方法(Object是Java中最大的类)。
      String - Object

    Object是父类,提供一些方法,其中包含toString()方法。所有类会直接或间接继承Object类中的方法。
    toString()方法可以重写。
    重写toString方法

    @Override
    	public String toString() {
    		return "Cat [name=" + name + ", age=" + age + "]";
    	}
    

    重写后,cat打印结果:

    Cat [name=猫猫, age=2]
    Cat [name=猫猫, age=2]   //交换前打印的结果
    Cat [name=猫猫, age=2]
    Cat [name=猫猫, age=2]   //交换后打印的结果
    

    像str打印的结果,是toString()重写了;而cat打印结果,toString()直接使用的是Object。

    例6(数值有效交换)

    代码

    建一个Cat类的文件:

    package com.neicuntu;
    
    public class Cat {
    	public static int count = 10; 
    	public String name = 猫猫";
    	public int age = 2;
    	
    	public static void run() {
    		System.out.println("跑跑跑");
    	}
    	
    	public void eat() {
    		System.out.println("吃吃吃");
    	}
    	@Override
    	public String toString() {
    		return "Cat [name=" + name + ", age=" + age + "]";
    	}
    }
    

    建一个Test类的文件:

    package com.neicuntu;
    
    public class Test{
    	public static void main(String[] args) {
    		//自定义的交换
    		Cat cat1 = new Cat();
    		Cat cat2 = new Cat();
    
    		cat1.name = "豆豆";
    		cat1.age = 5;
    		
    		cat2.name = "毛毛";
    		cat2.age = 3;
    		
    		System.out.println(cat1);
    		System.out.println(cat2);
    		change3(cat1, cat2);
    		System.out.println(cat1);
    		System.out.println(cat2);
    		
    	}
    	public static void change(Cat a,Cat b) {
    		int temp = a.age;
    		a.age = b.age;
    		b.age = temp;
    		
    		String str = a.name;
    		a.name = b.name;
    		b.name = str;
    	}
    }
    

    打印结果:

    Cat [name=豆豆, age=5]
    Cat [name=毛毛, age=3]
    Cat [name=毛毛, age=3]
    Cat [name=豆豆, age=5]
    

    内存图过程:

    文字解释:

    1. 执行第一次打印cat1和cat2之前的过程如下图:
      cat1和cat2入栈开辟空间
    2. change入栈出栈过程(进入堆区深处进行交换):
      • 开辟a、b的空间,分别指向0x88和0x99;
      • 开辟temp空间,将a中的age的值给temp:temp=5;
      • 将b中age值给a,cat1 0x88中的age为:age=3;
      • 将temp的值给b,cat2 0x99中的age为:age=5;
      • 开辟str空间,将a中的name的值给str:str=0x22;
      • 将b中的name值给a,cat1 0x88中的name为:name=0x33;
      • 将str的值给b,cat2 0x99中的name为:那么=0x22
        深入交换值

    图示:
    例6完整图示

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值