Java基础_虚拟机1:(JVM内存区域)

目录

1:什么是Java的虚拟机

2:Java虚拟机内存结构

2.1:内存结构

2.1.1:程序计数器

2.1.2:Java虚拟机栈(栈内存)

2.1.3:本地方法栈(和虚拟机栈合并,使用Native方法所开辟的内存空间)

2.1.4:堆内存(Java Heap)

2.1.5:方法区

2.1.6:运行时常量区

3:对象创建

4:对象的访问

4.1:句柄访问

4.2:指针访问(hotport采用指针访问的方法)

5:String案例分析

5.1:String案例

 5.2:String a=new String("hello")创建了几个对象?

5.3:基本数据类型案例 


1:什么是Java的虚拟机

HotSpot VM是Sun JDK和Open JDK所带的虚拟机,也是适用范围最广的虚拟机版本。也是我们默认语境讨论的虚拟机。

2:Java虚拟机内存结构

2.1:内存结构

Java虚拟机在执行Java程序的过程中会把他所管理的内存区域,划分成若干个不同的数据区域。这些数据区域都有自己各自的用途,以及创建和销毁时间,有些区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而创建和销毁。根据Java虚拟机规范1.7的规定,虚拟机所管理的内存区域图如下:

2.1.1:程序计数器

程序计数器是一块较小的内存空间,可以看做是当前此线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个技术计数器的值来选取下一条需要执行的代码指令,分支、循环、跳转、异常、线程恢复都需要这个计数器来完成

程序计数器特点:

1:是一块较小的内存空间,用来记录线程执行行号,

2:每一线程都有一个独立的程序计数器,切互不影响,独立存储,我们称这类内存区域的"线程私有"内存.

3:生命周期跟所在线程相同,没有内存溢出的情况。

2.1.2:Java虚拟机栈(栈内存)

虚拟机栈是Java方法执行的内存模型,每个方法在执行的过程中都会创建一个栈帧,用于存放局部变量表、操作数栈、动态链接、方法出口等信息。常常有人把Java的内存划分为堆内存和栈内存,而栈内存就是指虚拟机栈。其中的局部变量表属重点。用于存放各种基本数据类型和对象的引用。

栈内存的特点:

1:栈内存也是线程私有的,生命周期同线程相同,

2:Java虚拟机栈描述的是Java方法执行的内存模型,

3:每个线程的方法执行的过程中,虚拟机都会创建一个栈帧,用于存放局部变量表、操作数栈、动态链接、方法出口等信息

4:局部变量表存放了各种基本数据类型、对象的引用、不是对象本身,只是一个指向对象地址的指针

4:当线程要求的栈深度大于虚拟机所允许的深度时会抛出:StackOverflowEror(堆叠溢出)异常,大部分虚拟机的栈内存都是可以动态扩展的,当扩展时无法申请到足够的深度时,会有OutOfMemoryError(内存不足)异常

思考问题:问什么基本数据类型

代码实例如下:a1方法调用a2,当两个线程同时调用a1的时候在栈内存开辟的区域如下如所示

堆栈内存分配如下 

2.1.3:本地方法栈(和虚拟机栈合并,使用Native方法所开辟的内存空间)

本地方法栈是执行Native方法的内存空间,和虚拟机栈类似,在HotSpot VM中已经把这两种结构合并到了一起

例如Object的hashcode方法,就是本地的c语言实现方法,也是在栈中开辟内存。

2.1.4:堆内存(Java Heap)

堆内存(Java Heap)是虚拟机管理内存中最大的一块,Java堆是被所有的线程共享的一块内存区域,在虚拟机启动的时候创建,这个内存区域唯一的目标就是存放对对象的实例,所有所有的对象和数组都是在堆上分配的。Java堆是Java回收的主要区域,俗称垃圾堆,堆中还细分为老年代新生代。再细致一点有:Eden空间、From Survivor、To Survivor空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。

在内存分配的角度,可以划分为多个新程的私有分配缓冲区。Java堆是不连续的存储空间,可以扩展大小,通过-Xmx和-Xms来控制。当堆被占满的时候,无法为对象分配内存,会抛OutOfMemoryError(内存不足)异常.

堆内存特点:

1:所有线程共享,随着Java虚拟机启动而开辟,是GC主要的工作区域,简称垃圾堆。

2:主要用来存放对象的实例和数组都在堆中存储,所有线程共享,可划分出来多个线程的私有分配区,随着虚拟机启动而创建,可以调节大小,是虚拟机管理的最大内存区域,GC负责回收。

3:堆中的对象在垃圾分代回收算法中可以划分为:新生代、老年代。

4:如果堆中的空间不够分配新的对象实例,会报错内存溢出。可以通过-Xms和-Xmx扩展大小。

2.1.5:方法区

方法区和Java堆一样,是各种线程共享的内存区域,它用于存储被虚拟机加载的类信息、常量(final修饰)、静态变量、即时编译器编译后的代码等数据。虽然Java把方法区描述成堆的一个逻辑部分,但它却有一个别名叫做Non-Heap(非堆),目的是把Java堆区分出来。

方法区又叫做永久代,问什么叫做永久代呢?

是因为HotSpot(应用最广泛的虚拟机)虚拟机为了省事,想要将在堆中的gc回收机制(新生代和老年代)算法代码复用,直接用到方法区,所以把方法区叫做永久代,但是其他的虚拟机没有这种说法。

方法区特点:

1:存储类信息、静态变量、常量、即时编译后的代码等数据。

2:在HotSpot中称之为老年代。

2.1.6:运行时常量区

运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)

既然运行时常量池时方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。

运行时常量区特点:存储字面量和符号引用。

3:对象创建

对象创建的步骤,在我们用new关键字的时候,对象的创建步骤如下:

User user=new User();

1:类型检查(首先检查在常量池中是否能找到这个类的信息,是否已经被加载、解析和初始化过)

2:为对象分配内存,内存是固定的,因为你对象的各种类型字段都是起源于基本数据类型。对象所需要的内存在类加载之后便可以完全确定,采用如下两种方法划分新的内存空间。

               指针碰撞(内存是连续的通过指针摆动划分新的所需内存)

               空闲列表(内存不是连续的,有碎片化,虚拟机维护一个列表,这种请况从空闲列表中找到可用的内存)

3:对象初始化为零

4:执行对象的init方法,最终创建对象

4:对象的访问

当我们创建完对象之后是为了访问对象,通过在栈上的引用来操作堆上的具体对象,如何通过引用访问堆上的对象呢?不同的虚拟机有不同的策略。

4.1:句柄访问

 如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息; 

4.2:指针访问(hotport采用指针访问的方法)

 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象的地址。

这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。

5:String案例分析

5.1:String案例

尽量避免多个字符串拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。

字符串拼接:

        String str1 = "str";//第一行,会到常量池检测是否存在,目前不存在,常量池不存在 创建
        String str2 = "ing";//常量池

        String str3 = "str" + "ing";//常量池 创建新的字符串
        String str4 = str1 + str2; //在堆上创建的新的对象 对象+对象 就是新的对象
        String str5 = "string";//常量池中的对象
        System.out.println(str3 == str4);//false 常量池地址 不等于 对象地址
        System.out.println(str3 == str5);//true 常量池地址 等于 对象地址
        System.out.println(str4 == str5);//false 对象地址 不等于 常量池地址

 5.2:String a=new String("hello")创建了几个对象?

将创建 1 或 2 个字符串,如果在常量池存在了hello的值,那么直接在堆中创建字符串a。如果常量池不存在hello,首先在常量池中创建值hello,然后在堆中创建出对象a.

验证如下:

        String s1 = new String("abc");// 堆内存的地址值
        String s2 = "abc";
        System.out.println(s1 == s2);// 输出 false,因为一个是堆内存,一个是常量池的内存,故两者是不同的。
        System.out.println(s1.equals(s2));// 输出 true

结果:

false
true

5.3:基本数据类型案例 

  • Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean;这 5 种包装类默认创建了数值[-128,127] 的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。
  • 两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。(原因是浮点类型在某一个区间中有无数个数字  无法实现,new Double()
        Integer i1 = 33;//装箱(自动根据数值转换成包装类)  相当于调用了Integer.valueOf()方法
        Integer i2 = 33;
        int  tt=44;//拆箱(包装类转换成基本数据类型),相当于调用了intValve方法
        System.out.println(i1 == i2);// 输出 true
        Integer i11 = 333;
        Integer i22 = 333;
        System.out.println(i11 == i22);// 输出 false
        Double i3 = 1.2;
        Double i4 = 1.2;
        System.out.println(i3 == i4);// 输出 false

Integer 缓存源代码:

/**
*此方法将始终缓存-128 到 127(包括端点)范围内的值,并可以缓存此范围之外的其他值。
*/
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

应用场景:

  1. Integer i1=40;Java 在编译的时候会直接将代码封装成 Integer i1=Integer.valueOf(40);,从而使用常量池中的对象。
  2. Integer i1 = new Integer(40);这种情况下会创建新的对象。
  Integer i1 = 40;
  Integer i2 = new Integer(40);
  System.out.println(i1==i2);//输出 false

Integer 比较更丰富的一个例子:

  Integer i1 = 40;
  Integer i2 = 40;
  Integer i3 = 0;
  Integer i4 = new Integer(40);
  Integer i5 = new Integer(40);
  Integer i6 = new Integer(0);

  System.out.println("i1=i2   " + (i1 == i2));
  System.out.println("i1=i2+i3   " + (i1 == i2 + i3));
  System.out.println("i1=i4   " + (i1 == i4));
  System.out.println("i4=i5   " + (i4 == i5));
  System.out.println("i4=i5+i6   " + (i4 == i5 + i6));   
  System.out.println("40=i5+i6   " + (40 == i5 + i6));     

结果:

i1=i2   true
i1=i2+i3   true
i1=i4   false
i4=i5   false
i4=i5+i6   true
40=i5+i6   true

解释:

语句 i4 == i5 + i6,因为+这个操作符不适用于 Integer 对象,首先 i5 和 i6 进行自动拆箱操作,进行数值相加,即 i4 == 40。然后 Integer 对象无法与数值进行直接比较,所以 i4 自动拆箱转为 int 值 40,最终这条语句转为 40 == 40 进行数值比较。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值