3.jvm运行时数据区

3.1运行时数据区组成概述

在这里插入图片描述

  • 程序计数器

    程序计数器(Program Counter Register)是一块较小的内存空间,它可以

    看作是当前线程所执行的字节码的行号指示器

  • java虚拟机栈

    描述的是 Java 方法执行的内存模型,每个方法在执行的同时都会创建一个

    栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出

    口等信息,每个方法从调用直至执行完成的过程,都对应着一个栈帧在虚拟

    机栈中入栈到出栈的过程。

  • 本地方法栈

    与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地

    方法栈是为虚拟机调用 Native 方法服务的。

  • java堆

    是 Java 虚拟机中内存最大的一块,是被所有线程共享的,在虚拟机启动时

    候创建,Java 堆唯一的目的就是存放对象实例,几乎所有的对象实例都在这

    里分配内存,随着 JIT 编译器的发展和逃逸分析技术的逐渐成熟,栈上分配、

    标量替换优化的技术将会导致一些微妙的变化,所有的对象都分配在堆上渐

    渐变得不那么“绝对”了。

  • 方法区

    用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等

    数据

在这里插入图片描述

红色的为多个线程共享,灰色的为单个线程私有的

3.2程序计数器

1.概述

JVM 中的程序计数寄存器(Program Counter Register)中的 Register 命名源于

CPU 的寄存器,寄存器存储指令相关的现场信息.CPU 只有把数据装载到寄存器

才能运行

2.作用

程序计数器用来存储下一条指令的地址,也即将要执行的指令代码.由执行引擎读

取下一条指令

1. 使用程序计数器存储字节码指令地址有什么用?

为什么使用程序计数器记录当前线程的执行地址呢?

因为 CPU 需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪儿

开始继续执行.

JVM 的字节码解释器就需要通过改变程序计数器的值来明确下一条应该执行什么样的字节码指令.

2.程序计数器为什么被设定为线程私有的.

我们都知道所谓的多线程在一个特定的时间段内只会执行其中某一个线程的

方法,CPU 会不停地做任务切换,这样必然导致经常中断或恢复,如何保证分毫无

差呢?

为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然

是为每一个线程都分配一个程序计数器,这样一来各个线程之间便可以独立计算,

从而不相互干扰.

3.3java虚拟机栈

1.分清栈和堆

栈是运行时的单位,而堆是存储的单位

栈是线程私有的,堆是线程共享的

2.Java 虚拟机栈是什么

Java 虚拟机栈(Java Virtual Machine Stack),早期也叫 Java 栈.每个线程在创建

时都会创建一个虚拟机栈,其内部保存一个个栈帧,对应着一次方法的调用.

Java 虚拟机栈是线程私有的.

生命周期和线程一致

2.作用

主管 Java 程序的运行,它保存方法的局部变量(8 种基本数据类型,对象的引用地

址),部分结果,并参与方法的调用和返回

3.栈的特点

栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器.

JVM 直接对 java 栈的操作只有两个:调用方法,进栈.

执行结束后出栈.

对于栈来说不存在垃圾回收问题

4.栈中出现的异常

StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。

OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存

4.栈中存储什么

每个线程都有自己的栈,栈中的数据都以栈帧为单位存储.

在这个线程上正在执行的每个方法都各自对应一个栈帧.

栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息.

5.栈的运行原理
  • JVM 直接对 java 栈的操作只有两个,就是对栈帧的压栈和出栈,在一条活动的线程中,一个时间点上,只会有一个活动栈.即只有当前在执行的方法的栈帧(栈顶)是有效地,这个栈帧被称为当前栈(Current Frame),与当前栈 帧对应的方法称为当前方法(Current Method),定义这个方法的类称为当前 类(Current Class).

  • 执行引擎运行的所有字节码指令只针对当前栈帧进行操作.

  • 如果在该方法中调用了其他方法,对应的新的栈帧就会被创建出来,放在栈的顶端,成为新的当前栈帧.

  • 不同线程中所包含的栈帧(方法)是不允许存在相互引用的,即不可能在一个栈中引用另一个线程的栈帧(方法).

  • 如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果 给前一个栈帧,接着虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧. Java方法有两种返回的方式,一种是正常的函数返回,使用 return 指令,另一种是抛出异常.不管哪种方式,都会导致栈帧被弹出

5.栈帧的内部结构
  • 局部变量表

    局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局

    部变量。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,

    则存的是指向对象的引用

  • 操作数栈

    栈最典型的一个应用就是用来对表达式求值。在一个线程执行方法的过程

    中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可

    以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。

  • 动态链接

    因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个

    引用指向运行时常量

  • 方法返回地址

    当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保

    存一个方法返回地址。

6.面试题

什么情况下会出现栈溢出(StackOverflowError)?

栈溢出就是方法执行时创建的栈帧超过了栈的深度。那么最有可能的就是方法递归调

用产生这种结果

不能通过调整栈大小就让栈不溢出

分配的栈内存并不是越大越好,可能会影响其他内存空间

垃圾回收机制不会涉及到虚拟机栈

3.4.本地方法栈

  • Java 虚拟机栈管理 java 方法的调用,而本地方法栈用于管理本地方法的调用

  • 本地方法栈也是线程私有的.

  • 允许被实现成固定或者是可动态扩展的内存大小.内存溢出方面 也是相同的

  1. 如果线程请求分配的栈容量超过本地方法栈允许的最大容量抛出
  2. StackOverflowError.
  3. 如果本地方法可以动态扩展,并在扩展时无法申请到足够的内存会抛出
  4. OutOfMemoryError
  • 本地方法是用 C 语言写的.

  • 它的具体做法是在 Native Method Stack 中登记 native 方法,在Execution Engine 执行时加载本地方法库

3.5java堆内存

1.堆内存概述
  • 一个 JVM 实例只存在一个堆内存,堆也是 Java 内存管理的核心区域.

  • Java 堆区在 JVM 启动时的时候即被创建,其空间大小也就确定了,是 JVM 管理 的最大一块内存空间.

  • 堆内存的大小是可以调节.可能会溢出

  • 所有的线程共享 Java 堆,在这里还可以划分线程私有的缓冲区.

  • 在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除

  • 堆,是 GC(Garbage Collection,垃圾收集器)执行垃圾回收的重点区域.

2.为什么分区

将对象根据存活概率进行分类,对存活时间长的对象,放到固定区,从而减少扫

描垃圾时间及 GC 频率。针对分类进行不同的垃圾回收算法,对算法扬长避短

3.对象创建内存分配过程

1.new 的新对象先放到伊甸园去,此区大小有限制.

2.伊甸园的空间填满时,程序有需要创建对象,对伊甸园区进行

垃圾回收(Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁.再加载新

的对象放到伊甸园区.

3… 然后将伊甸园区中的剩余对象移动到幸存者 0 区.

4.如果再次出发垃圾回收,此 时上次幸存下来存放到幸存者 0 区的对象,如果没有回收,

就会被放到幸存者 1 区,每次会保证有一个幸存者区是空的

5.如果再次经历垃圾回收,此时会重新放回幸存者 0 区,接着再去幸存者 1 区

6.在老年区,相对悠闲,当养老区内存不足时,再次触发 GC:Major GC,进行养老区的内存

清理

8.若养老区执行了 Major GC 之后发现依然无法进行对象保存,就会产生 OOM 异常.

Java.lang.OutOfMemoryError:Java heap space

4.TLAB机制
为什么有 TLAB(Thread Local Allocation Buffer)

堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据.

由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间是线程

不安全的

为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度.

什么是 TLAB?

TLAB 的全称是 Thread Local Allocation Buffer,即线程本地分配缓存区,这是一个线

程专用的内存分配区域。

如果设置了虚拟机参数 -XX:UseTLAB,在线程初始化时,同时也会申请一块指定大小的

内存,只给当前线程使用,这样每个线程都单独拥有一个空间,如果需要分配内存,就在

自己的空间上分配,这样就不存在竞争的情况,可以大大提升分配效率。

JVM 使用 TLAB 来避免多线程冲突,在给对象分配内存时,每个线程使用自己的 TLAB,

这样可以避免线程同步,提高了对象分配的效率。

TLAB 空间的内存非常小,缺省情况下仅占有整个 Eden 空间的 1%,也可以通过选项

-XX:TLABWasteTargetPercent 设置 TLAB 空间所占用 Eden 空间的百分比大小。

5.字符串常量池

字符串常量池为什么要调整位置?

JDK7 中将字符串常量池放到了堆空间中。因为永久代的回收效率很低,在 Full GC 的时候

才会执行永久代的垃圾回收,而 Full GC 是老年代的空间不足、永久代不足时才会触发。

这就导致 StringTable 回收效率不高,而我们开发中会有大量的字符串被创建,回收效率低,

导致永久代内存不足。放到堆里,能及时回收内存。

3.6方法区

1.方法区的理解

方 法 区 , 是 一 个 被 线 程 共 享 的 内 存 区 域 。 其 中 主 要 存 储 加 载 的 类 字 节 码.class/method/field 等元数据、static final 常量、static 变量、即时编译器编译后的代码等数据。另外,方法区包含了一个特殊的区域“运行时常量池”。

Java 虚拟机规范中明确说明:”尽管所有的方法区在逻辑上是属于堆的一部分,但对于 HotSpotJVM 而言,方法区还有一个别名叫做 Non-Heap(非堆),目的就是要和堆分开.

所以,方法区看做是一块独立于 java 堆的内存空间

在这里插入图片描述

方法区在 JVM 启动时被创建,并且它的实际的物理内存空间中和 Java 堆区一样都可以

是不连续的.

方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展.

方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,

虚拟机同样会抛出内存溢出的错误:java.lang.OutOfMemoryError:Metaspace.

2.方法区,栈,堆的交互关系

在这里插入图片描述

3.方法区的内部结构

在这里插入图片描述

方法区它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编

译后的代码缓存,运行常量池等

4.类卸载

判定一个常量是否“废弃”还是相对简单,而要判定一个类型是否属于“不再被 使用的类”的条件就比较苛刻了。需要同时满足下面三个条件:

1.该类所有的实例都已经被回收,也就是 Java 堆中不存在该类及其任何派生子

类的实例。

2.加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加 载器的场景,如 OSGi、JSP 的重加载等,否则通常是很难达成的。

3.该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通 过反射访问该类的方法

5.常见面试题

说一下 JVM 内存模型吧,有哪些区?分别干什么的?

在这里插入图片描述

JVM 内存分布/内存结构?栈和堆的区别?堆的结构?为什么两个 survivor 区?Eden 和 survior 的比例分配

jvm 内存分区,为什么要有新生代和老年代

讲讲 vm 运行时数据库区

什么时候对象会进入老年代?

jvm 的方法区中会发生垃圾回收吗?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值