• 浅析Java里的内存分析及常量池加强对Java里字符串的理解


      本文将简单的说明下当我们运行Java程序时JVM(Java虚拟机)的内存分配情况。

    一、基础概念要点

      首先我们先来感观的认识下几个名词:

    1、栈空间(stack):连续的存储空间,遵循后进先出的原则,用于存放局部变量

      一般来说,基本数据类型直接在栈中分配空间,局部变量(在方法代码段中定义的变量)也在栈中直接分配空间,当局部变量所在方法执行完成之后该空间便立刻被JVM回收。

      还有一种是引用数据类型,即我们通常所说的需要用关键字new创建出来的对象所对应的引用也是在栈空间中,此时,JVM在栈空间中给对象引用分配了一个地址空间(相当于一个门牌号,通过这个门牌号就可以找到你家),在堆空间中给该引用的对象分配一个空间,栈空间中的地址引用指向了堆空间中的对象区(通过门牌号找住址);

    2、堆空间(heap):不连续的空间,用于存放new出的对象,或者说是类的实例

      一般用来存放用关键字new出来的数据。

    3、方法区(method):方法区在堆空间内,用于存放:(1)类的代码信息;(2)静态变量和方法;(3)常量池(字符串敞亮等,具有共享机制)。

      方法区在后面的jdk的版本中,已经不存在堆空间内了。而且方法区现在已经不叫方法区了,叫元空间,另外元空间从内存移到了磁盘。

    4、Java中除了基本数据类型,其他的均是引用类型,包括类、数组等等。

    5、数据类型的默认值  ——  基本数据类型默认值:

      数值型:0

      浮点型:0.0

      布尔型:false

      字符型:u0000

      引用类型:null

    6、变量初始化

      成员变量可不初始化,系统会自动初始化;

      局部变量必须由程序员显式初始化,系统不会自动初始化。

    二、实例代码进行内存分析

      创建类:分别是Student、Computer、Test,代码如下:

    public class Student {
        int score;
        int age;
        String name;
    
        Computer computer;
    
        public void study() {
            System.out.println("studying...");
        }
    }
    
    public class Computer {
        int price;
        String brand;
    }
    
    public class Test {
        public static void main(String[] args) {
            Student stu = new Student();
            stu.name = "xiaoming";
            stu.age = 10;
            stu.study();
    
            Computer c = new Computer();
            c.brand = "Hasse";
            System.out.println(c.brand);
    
            stu.computer = c;
            System.out.println(stu.computer.brand);
    //        System.out.println("----------------------------------------");
    //        c.brand = "Dell";
    //        System.out.println(c.brand);
    //        System.out.println(stu.computer.brand);
    //        System.out.println(stu.computer.brand == c.brand);
        }
    }

      代码分析:

      我们知道,程序的入口是main(),因而从main方法从上到下、从左到右进行分析。

    1、Student stu = new Student();

      (1)首先,Java虚拟机(JVM)去方法区寻找是否有Test类的代码信息,如果存在,直接调用。

      如果没有,通过类加载器(ClassLoader)把.class字节码加载到内存中,并把静态变量和方法、常量池加载(“xiaoming”、“Hasse”)。

      (2)走到Student,以同样的逻辑对Student类进行加载;静态成员;常量池(“studying”)。

      (3)走到stu,stu在main方法内部,因而是局部变量,存放在栈空间中。

      (4)走到new Student,new出的对象(实例),存放在堆空间中,以方法区的类信息为模板创建实例。

      (5)‘’=‘’赋值操作,把new Student的地址告诉stu变量,stu通过四字节的地址(十六进制),引用该实例。

      如下图:

    2、stu.name = “xiaoming”;

      (6)stu通过引用new Student实例的name属性,该name属性通过地址指向常量池的"xiaoming"。

    3、stu.age = 10;

      (7)stu实例的age属性是基本数据类型,基本数据类型直接赋值。

    4、stu.study();

      (8)调用实例的方法时,并不会在实例对象中生成一个新的方法,而是通过地址指向方法区中类信息的方法。

      6、7、8的过程如下图:

    5、Computer c = new Computer();

      同stu变量的生成过程。

    6、c.brand = “Hasse”;

      同stu.name = "xiaoming"过程。

    7、stu.computer = c;

      把c对象对Computer实例的引用赋值给Student实例的computer属性。亦即:该Student实例的computer属性指向该Computer类的实例。如下图:

    8、拓展  ——  改变brand的地址指向。

      为进一步理解,我们把注释内容去掉:重新将Computer实例的brand属性指向"Dell"常量,那stu.computer.brand指向谁呢?Dell还是Hasse?

    c.brand = "Dell";

      根据刚才的分析可知:stu通过地址引用Student实例,而该实例的computer的指向和c的指向是同一个Computer实例,因而改变该Computer实例的brand属性的指向,两者都会改变。

      举个例子:访问大明,和访问大明的儿子的爸爸,实质上访问的是同一个对象:大明。因而,最终的结果是true。

    三、理解字符串常量及常量池

      下面我们添加新的代码,如下:

    String str = "Dell";
    System.out.println(c.brand == str);

      结果会如何呢?

      根据常量池具有共享性,可知并不会生成新的常量"Dell",而是会把str通过地址指向原来的"Dell",因而结果是true。

      这里说一下扩展知识:与前端可能容易混淆

    1、八大基本数据类型里的:char 字符型,与string 字符串不要混淆,string字符串是个对象

      同样的有:int 整数类型,interger 也是个对象,

    2、原本字符串应该直接存在堆中的。但是因为频繁使用,就搞了个字符串常量池

      当String str="i"这样时,会存到字符串池里;

      而当String str=new String("i")这样则会存到堆里和字符串常量池各一份;

      所以问:String s = new String(“xyz”);创建了几个字符串对象?答案就是 2个。字符串池和堆中各一个对象。

    3、还有integer的时候也要注意,integer也有个缓冲区

      在-128~127之间的数值会存到缓冲区,其他的存到堆中。

      Integer是个对象,和int不一样,int存在栈中。

      Integer new出来的对象直接比较是不相等的。但是Integer a = 122;这种直接赋值并且在-128~127之间,用==比较是相等的。

      所以,一般要是用到integer的数值,如果不能确保一定在这个范围内,最好不要用==去判断是否相等。

    4、常量池好像有三个,一个是字符串常量池在堆中,运行时常量池、静态常量池在元空间里在磁盘上

      之前应该都在方法区,由于String使用比较`频繁`,所以给String在方法区设置了一个`字符串池`。之前在方法区,现在好像在堆中。

      由于String使用比较`频繁`,所以才在方法区设置了一个`字符串池`。后来别的池跟着方法区移走了,字符串池放在了堆中。(原本方法区和堆用的就是同一块内存)

    5、方法区更新记录

    参考文章链接:https://blog.csdn.net/qq_36743482/article/details/78527312

  • 相关阅读:
    002 课程导学
    006 实例1-温度转换
    003 Python基本语法元素
    PHP读取文件头(2字节)判断文件类型(转)
    常见的浏览器Hack技巧总结(转)
    晨曦之光 linux Crontab 使用(转)
    实用技巧:简单而有用的nohup命令介绍(转)
    shell之“>/dev/null 2>&1” 详解(转)
    phpmailer邮件类下载(转)
    PHP拦截器的使用(转)
  • 原文地址:https://www.cnblogs.com/goloving/p/14781804.html
Copyright © 2020-2023  润新知