JVM二运行时数据区

分代收集理论:当前商业虚拟机的垃圾收集器,大多数都遵循了"分代收集"(GenerationalCollection)的理论进行设计,分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则,它建立在两个分代假说之上:

  • 弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。
  • 强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。

这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则:收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。

  • 如果一个区域中大多数对象都是朝生夕灭,难以熬过垃圾收集过程的话,那么把它们集中放在一起,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量的空间;
  • 如果剩下的都是难以消亡的对象,那把它们集中放在一块,虚拟机便可以使用较低的频率来回收这个区域。

这就同时兼顾了垃圾收集的时间开销和内存的空间有效利用。

内存模型变迁:

JDK1.7:

  • Young 年轻区 :主要保存年轻对象,分为三部分,Eden区、两个Survivor区。
  • Tenured 年老区 :主要保存年长对象,当对象在Young复制转移一定的次数后,对象就会被转移到Tenured区。
  • Perm 永久区 :主要保存class、method、filed对象,这部份的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到OOM :PermGen space 的错误。
  • Virtual区: 最大内存和初始内存的差值,就是Virtual区。

JDK.1.8:

​ 由2部分组成,新生代(Eden + 2*Survivor ) + 年老代(OldGen ) JDK1.8中变化最大是,的Perm永久区用Metaspace进行了替换注意:Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中

JDK1.9:

​ 取消新生代、老年代的物理划分

​ 将堆划分为若干个区域(Region),这些区域中包含了有逻辑上的新生代、老年代区域

虚拟机栈

栈帧(Stack Frame)是用于支持虚拟机进行方法执行的数据结构。

栈帧 存储了方法的局部变量表操作数栈 、动态连接和方法返回地址 等信息。每一个方法从调用至执行完成的过程,都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程。

栈内存为线程私有的空间,每个线程都会创建私有的栈内存,生命周期与线程相同,每个Java方法在执行的时候都会创建一个栈帧(Stack Frame)。栈内存大小决定了方法调用的深度,栈内存过小则会导致方法调用的深度较小,如递归调用的次数较少。

栈异常的两种情况

如果线程请求的栈深度大于虚拟机所允许的深度 (Xss默认1m),会抛出StackOverflowError 异常

如果在创建新的线程时,没有足够的内存去创建对应的虚拟机栈 ,会抛出OutOfMemoryError异常【不一定】

本地方法栈

本地方法栈和虚拟机栈相似,区别就是虚拟机栈为虚拟机执行Java服务(字节码服务),而本地方法栈为虚拟机使用到的Native方法(比如C++方法)服务

方法区

方法区(Method Area)是可供各个线程共享的运行时内存区域,方法区本质上是Java语言编译后代码存储区域,它存储每一个类的结构信息 ,例如:运行时常量池、成员变量、方法数据、构造方法和普通方法的字节码指令等内容。很多语言都有类似区域。

方法区的具体实现有两种:永久代(PermGen)、元空间(Metaspace)

永久代和元空间的区别是什么?

  1. JDK1.8之前使用的方法区实现是永久代,JDK1.8及以后使用的方法区实现是元空间。
  2. 存储位置不同:
    永久代所使用的内存区域是JVM进程所使用的区域,它的大小受整个JVM的大小所限制。
    元空间所使用的内存区域是物理内存区域。那么元空间的使用大小只会受物理内存大小的限制。
  3. 存储内容不同:
    永久代存储的信息基本上就是上面方法区存储内容中的数据。
    元空间只存储类的元信息,而静态变量和运行时常量池都挪到堆中。

字符串常量池

1)三种常量池的比较
class常量池 :一个class文件只有一个class常量池

字面量:数值型(int、float、long、double)、双引号引起来的字符串值等

符号引用:Class、Method、Field等
运行时常量池 :一个class对象有一个运行时常量池

字面量:数值型(int、float、long、double)、双引号引起来的字符串值等

符号引用:Class、Method、Field等
字符串常量池 :全局只有一个字符串常量池

双引号引起来的字符串值

2)字符串常量池如何存储数据?

为了提高匹配速度, 即更快的查找某个字符串是否存在于常量池 Java 在设计字符串常量池的时候,还搞了一张StringTable, StringTable里面保存了字符串的引用。StringTable类似于HashTable(哈希表)。在JDK1.7+,StringTable可以通过参数指定-XX:StringTableSize=99991

字符串常量池如何查找字符串:

根据字符串的hashcode找到对应entry

如果没有冲突,它可能只是一个entry

如何有冲突,它可能是一个entry的链表,然后Java再遍历链表,匹配引用对应的字符串

如果找到字符串,返回引用

如果找不到字符串,在使用intern()方法的时候,会将intern()方法调用者的引用放入到stringtable

程序计数器

程序计数器(Program Counter Register),也叫PC寄存器,是一块较小的内存空间,它可以看作是当前线程所执行的字节码指令的行号指示器 。字节码解释器的工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支,循环,跳转,异常处理,线程回复等都需要依赖这个计数器来完成。
为什么需要程序计数器?

由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(针对多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换(系统上下文切换)后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为"线程私有"的内存。
存储的什么数据?

如果一个线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个Native方法,这个计数器的值则为空。

**异常:**此内存区域是唯一一个在Java的虚拟机规范中没有规定任何OutOfMemoryError异常情况的区域。

直接内存

直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中定义的内存区域。在JDK1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O 方式,它可以使用native 函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。本机直接内存的分配不会受到Java 堆大小的限制,受到本机总内存大小限制。

直接内存(堆外内存)与堆内存比较:

  • 直接内存申请空间耗费更高的性能,当频繁申请到一定量时尤为明显
  • 直接内存IO读写的性能要优于普通的堆内存,在多次读写操作的情况下差异明显

为什么会是这样?

从数据流的角度,来看

非直接内存作用链:本地IO -->直接内存-->非直接内存-->直接内存-->本地IO

直接内存作用链:本地IO-->直接内存-->本地IO
直接内存的使用场景:

有很大的数据需要存储,它的生命周期很长

适合频繁的IO操作,例如:网络并发场景

相关推荐
东阳马生架构2 小时前
G1原理—2.G1是如何提升分配对象效率
jvm
旷野..9 小时前
Java协程的引入会导致GC Root枚举复杂度大大增加,JVM是如何解决的呢?
java·开发语言·jvm
Evaporator Core10 小时前
SQLite 的未来发展与展望
jvm·性能优化·sqlite
蜗牛_snail10 小时前
JVM三JVM虚拟机
jvm
Evaporator Core13 小时前
SQLite 调试与性能优化指南
jvm·性能优化·sqlite
工业甲酰苯胺13 小时前
JVM实战—OOM的生产案例
jvm
杨荧1 天前
【开源免费】基于Vue和SpringBoot的贸易行业crm系统(附论文)
前端·javascript·jvm·vue.js·spring boot·spring cloud·开源
东阳马生架构1 天前
G1原理—1.G1回收器的分区机制
jvm
Chancezhou1 天前
【JVM】总结篇之对象内存布局 执行引擎
jvm