JVM(4)—运行时数据区

JVM(4)—运行时数据区

运行时数据区的组成

  • 线程私有
  1. 程序计数器:存储线程的执行位置。
  2. 虚拟机栈:存储Java方法调用与执行过程的数据。
  3. 本地方法栈:存储本地方法的执行数据。
  • 线程共享
  1. 堆:主要存储对象。
  2. 方法区:存储描述类/方法/字段等定义数据。
  3. 运行时常量区:保存常量数据。

程序计数器

  1. 记录当前线程所即将执行的字节码指令行号(即将要执行哪一行字节码指令)。

  2. 每一个线程拥有自己的计数器。

  3. 执行native本地方法时,程序计数器值为空。

  4. 占用内存极少,不会出现OOM(Out Of Memory Error)。

  5. 多线程时,CPU时间片切换后,程序计数器可以告诉CPU当前程序执行到了什么位置。

虚拟机栈

  1. 每一个栈帧对应了一个方法,每次方法调用都会产生对应的栈帧。

  2. 虚拟机栈生命周期与线程相同,所有方法执行完后,虚拟机栈就被销毁了,线程也结束了。

  3. 线程私有。

  4. 不会被垃圾回收。

  5. 栈深度(栈帧的数量,3个栈帧栈深度就是3)有限制。1.5后默认每一个线程的栈空间为1mb,之前256k。-Xss数值决定了栈的内存大小,决定栈的最大深度。

  6. 栈设置了固定大小(推荐)可能会发生 StackOverflowError:达到上限。

    栈可以动态扩展可能会发生 OutOfMemoryError:可用内存不足。

栈帧的组成

  • 局部变量表:局部变量
  • 操作数栈:保存中间计算的临时结果
  • 动态链接:将符号引用转为直接引用
  • 返回地址:存放调用方法的程序计数器的值

局部变量表

  1. 按定义顺序存储方法的参数方法内的局部变量

  2. 线程私有,方法调用被创建,方法退出时销毁。

  3. 编译期间长度已经确定,局部变量元数据存储在字节码中。

  4. 是栈帧中最主要的存储空间,决定了栈的深度。

  5. 存储局部变量的单位为Slot(变量槽)。构造方法和实例方法中,0号槽位默认是this,指向当前类的实例。静态方法中没有this关键字。

  6. 32位以内的类型(int/float/char/引用类型...)占1个Slot,64位类型(long/double)占2个Slot。

(Start PC:字节码指令起始行号,给局部变量分配槽。Length:字节码指令作用了x行,释放槽)

  1. 槽复用:将原本空闲出来的槽复用给新来的局部变量,来缩小局部变量表的尺寸。

操作数栈

字节码指令在执行过程中的中间计算结果存储在操作数栈中。

push压入操作数栈、store_1将操作数栈栈顶数值存入局部变量表的1号槽中、load_1读取局部变量表1号槽中的值压入操作数栈、add将操作数栈栈顶与第二个的值取出相加放入操作数栈栈顶、return将操作数栈栈顶数值取出返回。

64位数据类型占2个操作数栈空间。

动态链接

将保存在字节码中的符号引用转为运行时内存的直接引用。内存指针,指向方法区中方法的元数据。

方法返回地址

保存该方法调用者的程序计数器的值,用于方法执行后后续执行。

本地方法栈

native本地方法

native本地方法就是Java调用非Java代码的接口。

定义native本地方法时,不需要提供方法的实现。

native本地方法可以调用其他语言接口实现对操作系统更底层的操作。

native本地方法栈

HotSpot将本地方法栈与虚拟机栈合二为一。

  1. 存放运行时实例化的对象实例。new 出来的对象。
  2. JVM启动时就创建,堆内存也被分配。是JVM内存主要占用区域。
  3. 线程共享。堆内存在物理上可以分散,在逻辑上连续。
  4. 堆中包含线程私有的缓冲区(TLAB)用于提高JVM的并发处理效率。

JDK1.8 堆结构

  1. 新生代:用来存放新生的对象,该区域对象会被频繁GC。
  2. 老年代:新生代中的稳定对象放入老年代,该区域不会特别频繁GC。
  3. 元空间:内存的永久保存区域,主要存放类、方法的描述信息(元数据),几乎不GC。

设置堆的参数

  1. 设置堆空间大小参数
  • -Xms 设置堆空间(新生代+老年代)的初始内存大小。

  • -Xmx 设置堆空间(新生代+老年代)的最大内存大小。

  1. 默认堆空间大小
  • 初始内存大小 :物理电脑内存大小 / 64

  • 最大内存大小 :物理电脑内存大小 / 4

  1. 新生代与老年代的占用比例
  • 新生代占用1/3内存。

  • 老年代占用2/3内存。

  1. 手动设置堆空间
  • -Xms1g -Xmx1g
  • 建议将初始堆内存与最大堆内存设为相同的值。
  • 建议Xms与Xmx的值为老年代FillGC后存活对象的3-4倍。
  • -XX:+PrintGCDetails

对象分配

  1. 绝大数刚创建的对象会存入新生代的Eden伊甸园区。
  2. Eden伊甸园区与S0/S1的内存比例为 8:1:1 。

  1. Eden满时,会对年轻代进行垃圾回收,称为MinorGC。MinorGC的执行过程(复制交换算法):
  • 扫描Eden、From、To区域所有对象。
  • 在Eden区中如果对象可达(有引用),就给此对象添加个age=1的属性,将此对象复制到To区。
  • 在From区中如果对象可达,就给此对象age的值加1,将此对象复制到To区。
  • 清空Eden区与From区所有对象,From区变To区,To区变From区。
  • 当n次MinorGC后,对象的age超过了15(阈值),对象会从From区晋升到老年区。

MinorGC/FullGC/MajorGC

  1. 任何GC都会出发STW(Stop The World,全局停顿)。
  2. MinorGC只针对年轻代回收,执行效率高。
  3. FullGC,全堆回收,针对年轻代、老年代、方法区全面收集,执行效率低下,会导致系统长时间停滞,减少FullGC次数是JVM优化重点。
  4. MajorGC,只针对老年代回收,CMS垃圾收集器才会存在MajorGC。

GC日志分析

[GC (Allocation Failure) [PSYoungGen: 8192K->1000K(9216K)] 8192K->6792K(60416K), 0.0027928 secs] [Times: user=0.06 sys=0.03, real=0.00 secs] 

// GC : 不加类型默认为MinorGC
// (Allocation Failure) : 产生GC的原因,Eden空间分配不足
// PSYoungGen : GC的范围是年轻代
// 8192K->1000K(9216K) : GC前年轻代占用的空间->GC后年轻代占用的空间(年轻代总空间),MinorGC后只有To区域有对象。
// 8192K->6792K(60416K) : GC前堆占用的空间->GC后堆占用的空间(堆总空间)
//  0.0027928 secs : 本次GC使用时间

为什么年轻代总空间是9216K,而不是10240K?

答:年轻代组成是1个Eden和2个Survivor,只一个Survivor存放对象,所以总空间为Eden空间8192+1个Survivor空间1024=9216.

方法区

  1. 线程共享,物理上可以分散存储,逻辑为整体的内存区域。
  2. 保存了类加载信息、类信息(包含字段、方法)、即时编译器编译后的代码缓存、常量、静态变量(1.6版本以前)
  3. 方法区只是个概念,具体实现看JVM的不同的厂商。HotSpot的方法区实现为永久代(1.7版本),元空间(1.8版本)。

永久代与元空间的区别

  1. 规范说明方法区是堆的逻辑部分,但实现与堆内存无关,称为“非堆”。
  2. 永久代是jdk7和之前版本的方法区实现。
    • 特点:占用JVM内存保存数据,-XX:MaxPermSize有上限,加载的类太多会容易内存溢出,与其他JVM实现不一致。
  3. 元空间是jdk8之后的方法区实现。
    • 特点:使用本地内存保存数据,最大为可用物理内存,与其他JVM实现一致。

设置元空间

  • -XX:MetaspaceSize=xxx

设置元空间初始大小,默认大小与平台相关,到达进行调整并自动触发FullGC。

建议调整为一个较大的值,减少FullGC的可能。

  • -XX:MaxMetaspaceSize=xxx

设置元空间最大尺寸,默认为-1,代表最大可用内存,一般不设置最大值。

  • 超过最大值后会出现OOM:Metaspace

运行时常量区

将字节码文件中的常量池存放到运行时常量池中。

方法区的历史变化

posted @ 2020-08-25 20:18  Baby丿太依赖  阅读(167)  评论(0)    收藏  举报