深入了解JVM03.运行时数据区

5 篇文章 0 订阅

运行时数据区

运行时数据区在JVM中是一个大的区域,处理程序中的类,以及其执行流程等;包括程序计数器,虚拟机栈,本地方法栈,方法区,堆五个部分,各部分执行不同职能;
在这里插入图片描述

1. 程序计数器(Program Counter Register)

也叫做程序计数寄存器,其是对PC物理寄存器的模拟;

程序计数器用来存储下一条指令的地址,就是即将要执行的指令代码.

由执行引擎读取下一条指令;

它是一块很小的内存空间,几乎可以忽略不计,也是运行速度最快的存储区域.

在 JVM 规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与
线程生命周期保持一致.

任何时间一个线程都只有一个方法在执行,就是当前方法.

程序计数器会存储当前线程正在执行的Java方法的JVM指令地址.如果是在执行native方法,则是未指定值(undefined).

它是程序控制流的指示器,分支,循环,跳转,异常处理,线程恢复等基础功能都需
要依赖这个计数器来完成.

字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的
字节码指令.

它是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的
区域.

2. Java 虚拟机栈(Java Virtual Machine Stacks)

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

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

生命周期和线程一致.

主管 Java 程序的运行,它保存方法的局部变量(8 种基本数据类型,对象的引用地
址),部分结果,并参与方法的调用和返回.

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

JVM 直接对 java 栈的操作只有两个:调用方法–>压栈. 执行结束–>弹栈.

栈不存在垃圾回收.

栈中会抛出的异常

StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。
OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存。

栈的存储内容

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

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

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

栈的运行

栈的执行规则是后进先出,即最后加入的栈帧是最先进行执行的;
在这里插入图片描述

在一条活动的线程中,一个时间点上,只会有一个活动栈.即只有当前在执行的方
法的栈帧(处于栈顶位置)是有效地,这个栈帧被称为当前栈(Current Frame),与当前栈帧对应的方法称为当前方法(Current Method),定义这个方法的类称为当前
类(Current Class).

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

如果在该方法中调用了其他方法,对应的新的栈帧就会被创建出来,放在栈的顶
端,成为新的当前栈帧.从而保证了方法调用执行的准确性;

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

如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果
给前一个栈帧,接着虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧.

Java 方法有两种返回的方式,一种是正常的函数返回,使用 return 指令,另一种是
抛出异常.不管哪种方式,都会导致栈帧被弹出.

栈帧的结构

栈中存储着栈帧,那么栈帧是什么样的结构呢?
每个栈帧都有着相同的结构:
在这里插入图片描述

1.局部变量表(Local Variables)
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局
部变量。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,
则存的是指向对象的引用。

2.操作数栈(Operand Stack)
也称为表达式栈;在一个线程执行方法的过程中,就是进行计算的过程。程序中的所有计算过程都是在借助于操作数栈来完成的。

3.动态链接(Dynamic Linking)
在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个
引用指向运行时常量。

4.方法返回地址(Retuen Address)
当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保
存一个方法返回地址

5.其它附加信息

3.本地方法栈(Native Method Stack)

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

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

允许被实现成固定或者是可动态扩展的内存大小.

内存溢出方面也是相同的.

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

本地方法是用 C 语言写的.

它的具体做法是在本地方法栈中登记 native 方法,在执行引擎执行时加载本地方法库.

4.Java 堆内存

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

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

堆内存的大小是可以调节. 例如: -Xms:10m(堆起始大小) -Xmx:30m(堆最大内存大小) 一般情况可以将起始值和最大值设置为一致,这样会减少垃圾回收之后堆内存重
新分配大小的次数,提高效率.

《Java 虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但逻辑上它
应该被视为连续的.

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

《Java 虚拟机规范》中对 Java 堆的描述是:所有的对象实例都应当在运行时
配在堆上.

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

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

堆区域划分

Java8 及之后堆内存分为:新生区(新生代)+老年区(老年代)
新生区分为 Eden(伊甸园)区和 Survivor(幸存者)区

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

针对分类进行不同的垃圾回收算法,对算法扬长避短。

对象的创建

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

  2. 当伊甸园的空间填满时,程序有需要创建对象,JVM 的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁.再加载新的对象放到伊甸园区.

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

  4. 如果再次触发垃圾回收,此 时上次幸存下来存放到幸存者 0 区的对象,如果没有回收, 就会被放到幸存者 1 区,每次会保证有一个幸存者区是空的.

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

  6. 什么时候去养老区呢?默认是 15 次,也可以设置参-XX:MaxTenuringThreshold=

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

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

新生代老年代配置比例

  1. 默认-XX:NewRatio=2,表示新生代占 1,老年代占 2,新生代占整个堆的 1/3
  2. 可以修改-XX:NewRatio=4,表示新生代占 1,老年代占 4,新生代占整个堆的 1/5
  3. 当发现在整个项目中,生命周期长的对象偏多,那么就可以通过调整老年代的大小,来进行调优在 HotSpot 中,Eden 空间和另外两个 survivor 空间缺省所占的比例是 8 : 1 : 1,当然开发人员可以通过选项-XX:SurvivorRatio调整这个空间比例。

GC分类收集

HotSpot VM 的实现,它里面的 GC 按照回收区域又分为两大类型:
一种是部分收集, 一种是整堆收集.

部分收集:不是完整收集整个 java 堆的垃圾收集.其中又分为:

1.新生区收集(Minor GC/Yong GC):只是新生区(Eden,S0,S1)的垃圾收集.

2.老年区收集(Major GC / Old GC):只是老年区的垃圾收集.

3.混合收集(Mixed GC):收集整个新生区以及部分老年区的垃圾.

整堆收集(Full GC):收集整个 java 堆和方法区的垃圾收集. 整堆收集出现的情况:
System.gc();时
老年区空间不足
方法区空间不足

TLAB机制

Thread Local Allocation Buffer,即线程本地分配缓存区,这是一个线
程专用的内存分配区域;

JVM 使用 TLAB 来避免多线程冲突,在给对象分配内存时,每个线程使用自己的 TLAB,这样可以避免线程同步,提高了对象分配的效率;

TLAB 空间的内存非常小,缺省情况下仅占有整个 Eden 空间的 1%;

5.方法区

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

方法区还有一个别名叫做 Non-Heap(非堆),,方法区看做是一块独立于 java 堆的内存空间;

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

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

方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出, 虚拟机同样会抛出内存溢出的错误:java.lang.OutOfMemoryError:Metaspace.

在这里插入图片描述
方法区用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编
译后的代码缓存,运行常量池等。

方法区的垃圾回收

方法区的垃圾收集主要回收两部分内容:
运行时常量池中废弃的常量和不再使用的类型。

回收废弃常量与回收 Java 堆中的对象非常类似。

判定一个常量是否“废弃”还是相对简单,而要判定一个类型是否属于“不再被
使用的类”的条件就比较苛刻了。需要同时满足下面三个条件:
1.该类所有的实例都已经被回收,也就是 Java 堆中不存在该类及其任何派生子
类的实例。
2.加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如 OSGi、JSP 的重加载等,否则通常是很难达成的。
3.该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通
过反射访问该类的方法

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值