• 浅析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

  • 相关阅读:
    JavaScript 为字符串添加样式 【每日一段代码80】
    JavaScript replace()方法 【每日一段代码83】
    JavaScript for in 遍历数组 【每日一段代码89】
    JavaScript 创建用于对象的模板【每日一段代码78】
    html5 css3 新元素简单页面布局
    JavaScript Array() 数组 【每日一段代码88】
    JavaScript toUTCString() 方法 【每日一段代码86】
    位运算
    POJ 3259 Wormholes
    POJ 3169 Layout
  • 原文地址:https://www.cnblogs.com/goloving/p/14781804.html
Copyright © 2020-2023  润新知