Java 内存图

classLoader.png

memory.png

前置知识

一个 完整的 Java 程序运行过程会涉及以下内存区域:

  • 寄存器,JVM 内部虚拟寄存器,存取速度非常快,程序不可控
  • 栈,保存局部变量的值,包括:1. 用来保存基本数据类型的值;2. 保存类的实例,的指针(即对堆区对象的引用);3. 加载方法时的帧(frame)
  • 堆,存放动态产生的数据,比如 new 出来的对象。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆里,但是它们是共享该类的方法,并不是每创建一个对象就把成员方法复制一次。
  • 常量池,JVM 为每个已加载的类型维护一个常量池,常量池就是这个类型用到常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用。池中的数据和数组一样通过索引访问。由于常量池中包含了一个类型所以的对其他类型、方法、字段的符号引用,所以常量池在 JAVA 的动态链接中起了核心作用。常量池存在于堆中。
  • 代码段:用于存放从硬盘上读取的源程序代码
  • 数据段:用来存放 static 定义的静态成员。

预备知识

  1. 一个 Java 文件,只要有 main 入口方法,我们就认为这是一个 Java 程序,可以单独编译运行
  2. 无论是普通类型的变量还是引用类型的变量(俗称实例),都可以作为局部变量,他们都可以出现在栈中。只不过普通实例的变量在栈中直接保存它所对应的值,而引用类型的变量保存的是一个指向堆区的指针,通过这个指针,就可以找到这个实例在堆区对于的对象。因此,普通变量只在栈区占用一块内存,而引用变量要在栈区和堆区各占用一块内存。

调用过程演示

public class BirthDate{
    private int day;
    
    BirthDate(int a,int b,int c){
    }
    
    public void setDay(int day){
        this.day = day;
    }
}

public class Test{
    public void change1(int i){
        i = 1234;
    }

    public void change2(BirthDate b){
        b = new BirthDate(22, 2, 2004);
    }

    public void change3(BirthDate b){
        b.setDay(22);
    }
    
    public static void main(String[] args){
    // 1. JVM 自动寻找 main 方法,执行第一句代码,
    //2. 创建一个 Test 类实例,在栈中分配一块内存,
    //3. 存放一个指向堆区对象的指针zhizhen_test001
        Test test =  new Test();
    //4. 创建一个 int 型的变量 date,由于是基本类型,直接在栈中存放 date 对应的值 9 
        int date = 9;
    //5. 创建两个 BirthDate 的实例 d1,d2, 并在栈中存放了对应的指针指向各自的对象。它们在实例化时调用了有参数的构造参数,因此对象中有初始值。(注意,方法还是用同一份)
        BirthDate d1 = new BirthDate(7, 7, 1970);
        BirthDate d2 = new BirthDate(1, 1, 2000);
        //6. 调用 test 对象 change1 的方法,并且以 date 为参数。JVM 读到这段代码时,检测到 i 是局部变量,因此会把 i 放到栈中,并且把 date 的值赋给 i。在 change1 的方法内,`i=1234`,所以 JVM 把 i 的值更新成 1234
        test.change1(date);
        //7. change1 执行完成后,立即释放变量 i 所占的内存空间。—— 这就是后进先出。
        //8. 调用 test 对象的 change2 方法,以 d1 为参数。JVM 检测到 change2 方法中 b 参数为局部变量,立即把 b 加入栈中。由于 b 是引用类型的变量,它的值是 d1 中的指针,此时 b 和 d1 指向同一个堆中的对象。在 b 和 d1 之间传递的是指针。
       //9. change2 的方法中又实例化了一个 BirthDate 的对象,并且赋值给b,`b= new BirthDate(22, 2, 2004)`。在内部执行过程是:在堆区 new 了一个对象,并且把这个对象指针保存在栈中b对应空间,此时实例不再指向实例 d1 指向的对象,但是实例 d1 所指向的对象并无变化,这样无法对 d1 造成任何影响。
        test.change2(d1);
        //10. change2 方法执行完毕后,立即释放局部引用变量 b 所占用的栈空间,注意只是释放了栈空间,堆空间要等待自带回收。
        // 11.调用 test 实例的 change3 方法,以实例d2为例,JVM会在栈中为局部变量b 分配空间,并且把 d2 中的指针存放在 b 中,此时 d2 和 b 指向同一个对象。再调用实例 b 的 setDay 方法,其实就是调用 d2 指向的对象的 setDay 方法。
        test.change3(d2);
      // 调用实例 b 的 setDay 方法会影响 d2,因为二者存放的指针指向的是同一个堆区,因此 b 对属性的更改会影响 d2.因为两者指向的是同一个堆区。
      // change3 方法执行完毕,立即释放局部引用变量 b
    }
        }

小结

  1. 变量分为两种:基本类型和引用类型。基本类型的变量和引用类型的变量作为局部变量,都放在栈里,基本类型直接在栈里保存值,引用类型只保存一个指向堆区的指针,真正的对象在堆里。作为参数时,基本类型就直接传值,引用类型传指针。
  2. 分清什么是实例什么是对象。Class a = new Class(),此时 a 叫做实例,对象就是 Class。实例 a 是局部变量,引用类型,栈中保存的是指向 Class 对象的指针。new Class() 对象保存在堆中。
  3. 栈中的数据和堆中的数据销毁不是同步的。方法一旦结束,栈中的局部变量会立即销毁,但是堆中对象不一定销毁。因为可能有其他变量指向了这个对象,直到栈中没有变量指向堆中的对象时,它才销毁,而且还不是马上销毁,要等垃圾回收扫描时才可能被销毁。
  4. 以上的栈、堆、代码段、数据段等等都是相对于应用程序而言的。每一个应用程序都对应唯一的一个 JVM 实例,每一个 JVM 实例都有自己的内存区域,互不影响。并且这些内存区域是所有线程共享的。这里提到的栈和堆都是整体的概念,这些堆栈还可以细分。
  5. 类的成员变量在不同对象中各有不同,都有自己的存储空间(成员变量在堆中的对象中)。而类的方法确实该类所有对象共享的,只有一套,对象使用方法时方法才压入栈,方法不使用则不占用内存
  6. 常量池。常量池它维护了一个已加载类的常量


    jvm.png

常量池

+8大基本类型:byte, char, int, long, short, float , double, boolean,

  • 基本类型的包装类:Byte,String,Integer,Short,Long,Boolean

基本类型和包装类的区别

基本类型存储在栈中,而基本类型包装类存储在堆中。上面提到的这些包装类都实现了常量池技术,另外两种浮点数类型的包装类则没有实现。另外,String 也实现了常量池技术。

    public static void objPoolTest(){
        int i = 40;
        int i0 = 40;
        Integer i1 = 40;
        Integer i2 = 40;
        Integer i3 = 0;
        Integer i4 = new Integer(40);
        Integer i5 = new Integer(40);
        Integer i6 = new Integer(0);
        Double d1 = 1.0;
        Double d2 = 1.0;
        String s = "s";
        String ss = new String("s");

        System.out.println("s == ss "+(s == ss));
        System.out.println("i=i0 " + (i==i0));
        System.out.println("i1==i2 "+ (i1==i2));
        System.out.println("i1=i2+i3 "+(i1==i2+i3));
        System.out.println("i4=i5 "+(i4==i5));
        System.out.println("i4=i5+i6 "+(i4==i5+i6));
        System.out.println("d1=d2 "+(d1==d2));
    }

s == ssfalse
i=i0 true
i1==i2 true
i1=i2+i3 true
i4=i5 false
i4=i5+i6 true
d1=d2 false
  1. i 和 i0 都是普通类型 int 的变量,所以数据直接存储在栈中。
  2. 常量池维护的是,-128至127;这点和 python 一样
  3. String 也实现了常量池技术,添加新的 String 之前都先去检查常量池中是否已存在,存在则添加。
  4. new String("s") / new Integer(3) , 是生成了一个新的对象。new 的作用就是声明要在堆中开辟一块新的内存空间。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容