通俗易懂理解java内存

通俗易懂理解java内存

理解了内存,就理解了一切! 这是我之前看到一个视频里面老师经常说的一句话,在当初听到这句话还没有什么感悟,只是当做一句很普通的感悟而已。一年多过去了,也算写了一些代码,再回过头来看JavaSE部分的知识时,才发现这句话的重要性。这简直就是对javase最直白简...

理解了内存,就理解了一切!

这是我之前看到一个视频里面老师经常说的一句话,在当初听到这句话还没有什么感悟,只是当做一句很普通的感悟而已。一年多过去了,也算写了一些代码,再回过头来看JavaSE部分的知识时,才发现这句话的重要性。这简直就是对javase最直白简洁又富有深意的总结。

在理解内存之前我们需要知道的预备知识既基本数据类型引用数据类型,java程序在内存中的运行就是对这两种数据类型的操作。如下图所示:

image.png

java内存博大精深,如果仔细深究肯定够我们学习好多天了。作为初学者,我们学习内存的目的是为了更好的理解代码背后的运行原理,写出更加健壮且安全的代码,所以我们只需要知道如下图所示的内存区域,便可以让我们达到目的。

image

从上图我们看到java虚拟机给我们分配的内存区域有栈空间,堆空间和方法区。栈空间里面主要存放基本数据类型和对象的引用;堆空间里面主要存放new出来的对象;方法区中包括Class信息、静态变量和常量池,常量池中主要存放常量,如字符串常量,基本包装类常量等。

现在,以创建一个Student对象为例,先创建一个Student类,如下代码:

package com.bjsxt.test;
/**
 * 定义一个Student类
 *
 */
public class Student {
    //静态变量
    public static int i = 5;
    //属性
    private int id;
    private String name;
    private char gender;
    //构造方法
    public Student() {}
    public Student(int id, String name, char gender) {
        this.id = id;
        this.name = name;
        this.gender = gender;
    }
    //get/set方法
    public int getId(){
        return id;
    }
    public void setId(int id){
        this.id = id;
    }
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public char getGender(){
        return gender;
    }
    public void setGender(char gender) {
        this.gender = gender;
    }
    //生成toString方法
    public String toString() {
        return "Student[id="+id+",name="+name+",gender="+gender+"]";
    }
}

再定义一个Test类,new一个Student对象

package com.bjsxt.test;
/**
 * 定义一个测试类,创建一个Student对象
 *
 */
public class Test {
    public static void main(String[] args) {
        //创建一个变量i
        int i = 12;
        //创建Student对象
        Student stu = new Student(101, "小明", '男');
    }
}

在上面的代码中,我们在Test类中定义了一个Student类的实例对象,然后再创建了一个局部变量i那么在内存中是什么样子的呢,请看下图:

image

从上图中,我们可以看到:局部变量存储在栈空间中,stu在栈空间中存储的是对象Student的引用地址值,既对象的引用也是存储在栈空间中;而new出来的对象则存储在堆空间中,注意Student类中定义了一个静态变量i,它从属于类,被所有Student实例对象共享,存储在方法区中;因为Stirng类型的变量属于字符串常量,存储在常量池中,所以name存储的内容是字符串”小明”的引用地址。

通过上面的展示,大家是否对java对数据在内存中的存储有了深刻的认识,感觉java内存是不是很神奇呢,简单的创建一个变量或者new一个对象就会在内存中出现这么多有趣的变化。如果大家感觉还是似懂非懂,那么我们再通过下面的一个例子对java内存进行更深入的应用,从而更好的理解java内存的运行原理。

首先看如下代码

package com.bjsxt.test;
/**
 * 定义一个测试类
 *
 */
public class Test2 {
    public static void main(String[] args) {
        //创建一个Student对象
        Student stu = new Student(101, "小明", '男');
        //创建一个字符串
        String str = "hello";
        //调用change()方法
        change(stu, str);
        //打印str和stu
        System.out.println("str="+str+", stu="+stu);
    }
    //change方法
    public static void change(Student stu, String str) {
        str = "welcome";
        stu.setGender('女');
        stu = new Student(102, "小红", '女');
    }
}

最后打印的结果是 :str=hello, stu=Student[id=101,name=小明,gender=女]

看到这个结果,大家是否很惊讶,str被赋予了一个新的字符串常量,应该是”welcome”才对吧,怎么还是”hello”,又或者stu应该是新赋予的对象,怎么还是原来这个对象呢。那么接下来,我们再通过内存图画一遍程序的执行过程,就能洞悉其中的原理。

程序的执行过程如下图所示:

1.在main方法中未执行change()方法之前的图片

image

2.在main方法中执行change()方法时

image

在上图中我们可以看到在调用change()方法中的执行过程,这个过程可以这样解释:

1.main方法中的变量stu把对象Student地址赋值给了chang方法中的变量stu,变量str把字符串”hello”的地址赋值给了change方法中的变量str;

2.根据str=”welcome”这行代码,str的指向地址由原来的0x12变成了0x22,指向常量池中的字符串”welcome”;

3.根据stu.setGender(‘女’)这行代码,stu指向的Student对象修改了变量gender由’男’变成了’女’;

4.根据stu = new Student(102, “小红”, ‘女’)这行代码,stu指向的对象的地址由0x101变成了0x102;

5.根据stu.i = 6这行代码,因为静态成员变量从属于类,任何Student的实例对象都可以共享该变量,所以change方法中的变量stu指向的Student实例对象也可以对静态变量i进行修改变成了6。

change方法执行完毕之后,进入main方法继续执行,接下来的内存就变成了如下图所示的情况:

image

change方法执行完毕,changge方法中的变量stu和str都会消失,new出来的0x102指向的对象没有被任何变量引用,虽然不会马上消失,但是也会被java垃圾回收器回收,最后的内存在main方法中就变成了上面这个样子,于是打印出来的结果就是我们上面看到的结果。

这就是比较典型的java程序内存的执行过程,作为一名程序员,这也是我们需要掌握的基本技能,使我们写代码、理解代码的的有效手段。当然,上面的知识点只是抛砖引玉,java内存的知识博大精深,远不是现在看到的这样简单,大家如果对java虚拟机中内存的分配有更多的兴趣,可以到网上找找相关资料学习,也可以买java虚拟机的相关书籍学习,比如《深入理解java虚拟机》等书籍。

0

评论0

鱼翔浅底,鹰击长空,驼走大漠
没有账号? 注册  忘记密码?