zoukankan      html  css  js  c++  java
  • Java学习笔记-内存分析

    一般方法调用的内存图像

    例子

    //只要是类名就一定是标识符
    //方法调用时,参数传递是‘值传递’
    public class Test{
    	public static void main(String[] args){
    		int a = 10;
    		int b = 20;
    		int retValue = sumInt(a,b);
    		System.out.println("retValue: " + retValue);
    	}
    	public static int sumInt(int a,int b){
    		int result = a + b;
    		int num = 3;
    		int retValue = divide(result, num);
    		return retValue
    	}
    	public static int divide(int x,int y){
    		int z = x / y;
    		return z;		
    	}
    }
    

    类创建时的内存图像

    概述

    1.类体 = 属性 + 方法

    • 属性描述的是 : 状态
    • 方法描述的是 : 行为动作
      2.各种变量的含义
    • 由于变量定义在类体当中,方法体之外,这种变量称为成员变量
    • 类似于学生序号这样的成员变量,必须通过创建对象去访问,所以这种成员变量有称为实例变量
    • 对象又称为实例,实例变量又被称为对象变量【对象级别的变量 】
    • 实例变量存在在java对象的内部,创建一个对象就有一份,100个就有100份
    • 想要访问实例变量,必须要有对象,不能通过"类名"的方式访问
      3.java运算符 -new
    • 实例化对象的语法:new 类名()
    • new运算符的作用是创建对象,在JVM堆内存中开辟新的内存空间
    • 方法区内存:在类加载的时候,class字节码代码片段被加载到该内存空间中
    • 栈内存:方法代码片段执行的时候,会给该方法分配内存空间,在栈内存中压栈
    • 堆内存:new的对象在堆内存中存储

    例子

    public class Students{
    	int age;
    	boolean sex;
        //姓名
        //Sting是一种引用数据类型,代表字符串
    	//name是一个实例变量
    	//name是一个引用
    	String name;
    
    	//家庭住址
    	//Address是一种引用数据类型,代表家庭住址
    	//addr是一个实例变量
    	//addr是一个引用
    	Address addr;
    	
    }
    public class Address{
    	String city;
    	Stirng Street;
    }
    public static void Test(Stirng[] atgs){
    	Students s1 = new Students("Zhang San",20,true);
    	
    }
    

    java语言中的三大变量

    JVM主要包括三块内存空间,分别是:栈内存、堆内存、方法区内存

    1. 堆内存和方法区内存各有1个,而栈内存是一个线程对应一个栈内存

    2. 只有局部变量不存在线程安全问题,实例变量和静态变量都存在线程安全问题,原因如1.所述,只有局部变量不会被多个线程共享。

    3. 方法的代码片段以及整个类的代码片段都被存储到方法区内存中,在在类加载的是时候这些代码片段会载入

    4. 方法调用的时候,该方法所需要的内存空间在栈内存中分配。在该方法执行结束之后,该方法所属的内存空间释放

    5. 栈中主要存储的是方法体当中的局部变量

    6. 在程序执行过程中使用new运算符创建的java对象,存储在堆内存当中,对象内部有实例变量,所以实例变量存储在堆内存中

    7. 变量分类:

    • 局部变量 【方法体中声明】
    • 成员变量 【方法体外声明】
      • 实例变量 【不加static】
      • 静态变量 【加static】
    1. 静态变量存储在方法区内存
    2. 三块内存中变化最频繁的是栈内存,最先有数据的是方法区内存,垃圾回收器主要针对的是堆内存
    3. 垃圾回收器【自动垃圾回收机制,GC机制】什么时候会考虑将某个java对象的内存回收呢?
    • 当堆内存当中的java对象成为垃圾数据的时候,会被垃圾回收器回收
    • 什么时候堆内存中的java对象会变成垃圾呢?
      • 没有更多的引用指向它的时候
      • 这个对象无法被访问,因为访问对象只能通过引用的方式访问

    例子

    public class Chinese{
    	String idCard;
    	String name;
    	Static String country = "中国";
    	
    	public Chinese(String idCard, String name){
    		this.idCard = idCard;
    		this.name = name;
    	}
    }
    public static void main(String[] args){
    	Chinese c1 = new Chinese("1243434353","张三");
    	Chinese c2 = new Chinese("7423983949","李四");
    }
    

    关键字 -this的内存图像

    1. this是一个变量,是一个引用。this保存当前对象的内存地址,指向自身。
    2. this存储在当前对象的堆内存中
    3. this可以使用在实例方法中,也可以使用在构造方法中,不能使用在静态方法中
    4. this()这种语法只能出现在构造方法的第一行,表示当前构造方法调用本类中其他的构造方法
      • 目的:如果不存在这个语法,那么就需要对每一属性进行赋初值/默认值,而有了这个语法就可以实现代码复用

    关键字 -super 子类继承时的内存图像

    例子:

    public class SuperTest03{
    	public static void main(String[] args){
    		CreditAccount ca1 = new CreditAccount();
    		
    		CreditAccount ca2 = new CreditAccount("1111", 1000.0, 0.999);
    	}
    }
    
    class Account{
    	private String actno;
    	private double balance;
    	
    	public Account(){
    		//super();
    		//this actno = null;
    		//this.balance = 0.0;
    	}
    	public Account(String actno, double balance){
    		this.actno = actno;
    		this.balance = balance;
    	}
    }
    
    class CreditAccount extends Account{
    	private double credit;
    	
    	public CreditAccount(){}
    	
    	public CreditAccount(String actno, double balance, double credit){
    		//super()存在的意义在构造方法中:不是代码复用,而是可以间接的在子类中方法父类的私有属性
    		//但是父类的私有属性又被子类继承,是存在于子类对象中的,详见内存图描述
    		super(actno, balance);
    		this.credit = credit;
    	}
    }
    

    补充:子类对父类的继承

    子类是可以继承父类的所有方法和属性的,包括私有方法和私有属性。
    当一个子类对象被创建的时候,首先会在内存中创建一个父类对象,然后在父类对象的外部放上子类独有的属性,这个两者结合起来形成了一个子类的对象。
    子类是拥有父类的私有属性和方法,但无法直接使用。
    1.两种父类的私有属性的访问方法:

    • 在子类构造方法可以通过super关键字访问;super(),括号中调用父类的相同参数的构造函数
    • 父类中有get和set方法,子类可以通过调用this.方法名访问父类的私有属性。

    2.间接访问父类的私有方法:
    当子类继承父类时,子类会继承父类所有的方法(不管这个方法的访问权限是private、protected、public的);
    只不过在子类自己定义的方法中无法直接去访问继承而来的private方法罢了,但是可以通过继承而来的public及protected方法来间接访问继承而来的private方法。
    即可以简单的将子类的方法分为两个版本,一是继承自父类的方法(简称继承版),二是自己定义的方法(简称自定版义版);
    但是不管是继承版的还是自定义版的方法,它们都是属于这个子类的。
    所以当子类实例化一个对象去调用这些方法时,这些方法中的this变量肯定指向这个对象本身(只不过访问继承版的private方法时,需要绕一点弯路)

    例子:

    class Father {
    	public Father() {
    		System.out.println("Father");
    	}
    	
    	private void hello(Son son) {
    		System.out.println("hello");
    		System.out.println(this==son);//输出true,说明this和son引用的是同一个对象
    	}
    
    	public void sayHello(Son son) {
    		System.out.println(this instanceof Son);
    		this.hello(son);
    	}
    }
    
    public class Son extends Father {
    	
    	public static void main(String[] args) {
    		Son s = new Son();
    		s.sayHello(s);
    	}
    }
    

    数组的内存图像

    1.Java语言中的数组是一种引用数据类型
    2.数组当中可以存储基本数据类型的数据,也可以存储引用数据类型的数据
    3.数组因为是引用数据类型,所以数组对象是在堆内存中的

    String类相关的内存图像

    1.String表示字符串类型,属于引用数据类型,不属于基本数据类型
    2.java中规定,双引号括起来的字符串,是不可变的
    3.在java中随便使用双引号括起来的都是String对象。例如:"abc", "def"
    4.在JDK当中双引号括起来的字符串,都是直接存储在方法区的"字符串常量池"当中的

    • 因为实际使用中,字符串使用的太频繁。为了执行效率,所以放到字符串常量池

    5.在学习完String类后,发现之前的String类内存图像的画法是有问题的

    示例1 - 静态和动态创建字符串的内存图像

    1.静态创建:

    • 双引号括起来的都在字符串常量池中有一份

    2.动态创建:

    • 不仅在字符串常量池有一份,且在new对象的时候一定在堆内存当中开辟空间
    public class StringTest01 {
        public static void main(String[] args) {
            //这两行代码表示底层创建了3个字符串对象,都在字符串常量池中
            String s1 = "abcdef";
            String s2 = "abcdef" + "xy";
    
            String s3 = new String("xy");
        }
    }
    

    在下图可以看出静态和动态创建的区别:

    示例2 -类的属性是String类

    public class StringTest02 {
        public static void main(String[] args) {
    
            User u = new User("张三",110);
        }
    }
    
    class User{
        String name;
        int id;
    
        public User() {
        }
    
        public User(String name, int id) {
            this.name = name;
            this.id = id;
        }
    }
    

    为什么判断字符串相等需要使用equals方法?

    通过String类内存图的学习,可以得出答案,如下示例程序:

    public class StringTest03 {
        public static void main(String[] args) {
            //"hello"存储在字符串常量池中,所以不会再新创建一份了
            String s1 = "hello";
            String s2 = "hello";
            //内存地址相同,True
            System.out.println(s1 == s2);
    
            String s3 = new String("xy");
            String s4 = new String("xy");
            //堆内存中对象的内存地址不同,false
            System.out.println(s3 == s4);
    
            //通过以上例子,说明字符串的比较使用equals方法才能万无一失
    
            //System.out.println(s1.equals("testString"));
            //这种写法不保险,可能出现空指针异常,推荐以下写法:
            System.out.println("testString".equals(s1));
        }
    }
    
  • 相关阅读:
    Android开发开源一款结合databinding写的用于RecyclerView的简单高效MultiTypeAdapter
    Android开发databinding和RecyclerView.ViewHolder的完美结合
    Android开发华为手机不弹出Toast,报HwRTBlurUtils: check blur style for HwToast-Toast...的原因
    android开发Toolbar标题居中显示的解决方法
    记录使用xshell通过ssh方式连接Linux机器的步骤
    同一局域网内手机访问电脑本地localhost网页的方法
    Gradle里面的依赖implementation和api的真正理解
    Android开发使用kotlin编写的泛型模式的MVP框架
    nyoj-3-多边形重心问题(求多边形面积和中心)
    nyoj-1132-promise me a medal(求线段交点)
  • 原文地址:https://www.cnblogs.com/zy200128/p/12633581.html
Copyright © 2011-2022 走看看