JVM知识体系-02内存结构
2023-02-12 本文已影响0人
云芈山人
一、思维导图
02.内存结构.png二、大纲
为啥要了解?
服务器出现性能问题时可快速了解哪块区域出现问题,并快速解决
2.1 都是些什么?
内存结构布局
1.内存结构布局.png三大块
堆内存
- JVM最大一块
- 年轻代(8:1:1)
- Eden空间(8)
- From Survivor空间(1)
- To Survivor空间(1)
- 老年代
方法区
- 存储类信息、常量、静态变量等数据
- 线程共享的区域
- Non-Heap(非堆)
栈
- java虚拟机栈
- 本地方法栈
内存存放示意图
各区放置内存Deam.png通过参数来控制各区域内存大小
2.参数控制内存大小.png控制参数
- -Xms
设置堆的最小空间大小 - -Xmx
设置堆的最大空间大小 - -XX:NewSize
设置新声带最大空间大小 - -XX:MaxNewSize
设置新生代最大空间大小 - -XX:PermSize
设置永久代最小空间大小 - -XX:MaxPermSize
设置永久代最大空间大小 - -Xss
设置每个线程的堆栈大小
注意
没有设置老年代参数,但可间接控制。老年代大小=堆空间大小-年轻代空间大小。
2.2 更高维度看JVM与系统调用的关系
JVM内存模型.png各个区域作用
Java堆(Heap)
- 内存最大的一块
- 线程共享,虚拟机启动时创建
- 唯一目的:存放对象实例
- 所有的对象在实例化后的整个运行期内,都被存放在堆内存中
- GC管理的主要区域(GC堆)
- 可处物理不连续但逻辑连续的内存空间中
-
堆中无内存完成实例分配,且堆无法再扩展,则抛出OutOfMemeryError异常
堆内存.png
方法区(Method Area)
- 线程共享
-
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
各JDK版本的常量位置.png - 虽JVM规范将其描述为堆的一个逻辑部分,但它别名为非堆,为了与Java堆区分,Hotspot虚拟机上,又习惯将其称为“永久代”,本质并不相同,仅因HotSpot设计团队将分代收集扩展至方法区或用永久代来实现方法区
- JVM规范对此区域限制非常宽松
- 不需要物理连续的内存
- 可选择固定大小或可扩展
- 可选择不实现垃圾收集
- GC在此区域较少出现,并非数据进入方法区就“永久”存在了,而是此区域内存回收目标主要针对常量池的回收和对类型的卸载,成绩难以令人满意,尤其是类型的卸载,条件非常苛刻,但又确实必要。
- 方法区无法满足内存分配需求时,抛出OutOfMemoryError异常
- java.lang.OutofMemoryError:PermGen space
- java.lang.OutOfMemoryError:Metaspace
- 又称方法区为“持久代”
- 方法区的大小决定了系统可以保存多少个类
- 造成方法区溢出的原因
- 加载大量的第三方的jar包
- Tomcat部署的工程过多
- 大量动态的生成反射类
- JDK1.6 中定义大量的字符串
- 关闭JVM就会释放这个区域的内存
程序计数器(Program Counter Register)
- 较小的内存空间
- 作用:当前线程所执行的字节码的行号指示器
- 虚拟机的概念模型中,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖计数器
- JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,任何一个确定时刻,一个处理器(多核则指一个内核)只会执行一条线程中的指令,故而为线程切换后能恢复到正确的执行位置,则每条线程都需一个独立计数器,互不影响,称为“线程私有”的内存
- 线程正执行一个Java方法,此计数器记录的为正在执行的虚拟机字节码指令的地址,若为Native方法,则计数器值为空(Undefined)
- 唯一一个在虚拟机规范中没有规定任何OutOfMemoryError情况的区域
JVM栈(JVM Stacks)
- 线程私有
- 生命周期与线程相同
- 描述的是Java方法执行的内存模型:每个方法被执行时会同时创建一个栈帧(Stack Frame)
- 每个方法被调用直至执行完成,即是栈帧从入栈到出栈的过程
- 两种异常状况
线程请求的栈深度大于虚拟机所允许的深度(StackOverflowError异常)
若虚拟机栈可动态扩展,扩展无法申请足够内存(OutOfMemoryError异常)
栈帧
虚拟机栈.png1. 局部变量表
存储类型
- 编译期可知的各种基本类型
boolean(1个solt)
byte(1个solt)
char(1个solt)
short(1个solt)
int(1个solt)
float(1个solt)
long(2个solt)
double(2个solt) -
对象引用(reference类型)
可指向对象起始地址的引用指针
直接指针访问对象.png
也可指向一个代表独享的句柄或相关的位置
句柄访问对象.png - returnAddress类型
指向一条字节码指令的地址
2. 操作栈
3. 指向运行时常量池的引用
4.方法出口
5. 动态链接
本地方法栈(Native Stacks)
- 作用与虚拟机栈形式,区别本地方法栈为虚拟机使用的Native方法服务
- 虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并无强制规定
- 两种异常状况
StackOverflowError异常
OutOfMemoryError异常
元空间
- 基于通道(Channel)和缓冲区(Buffer)的I/O方式,可用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里的DirectByteBuffer对象作为这块内存的引用进行操作。能提高性能,避免在Java堆和Native堆中来回复制数据。
其它
- 方法的执行都伴随线程。原始类型的本地变量及引用存放在线程栈中。引用关联的对象如String,存在在堆中。
-
可用JConsole查看
JConsole工具查看运行中的Java程序.jpg
OutOfMemoryError异常的原因
- OutOfMemoryError: Java heap space
原因:对象不能被分配到堆内存中 - OutOfMemoryError: PermGen space
原因:类或方法不能被加载到老年代。可能出现在一个程序加载很多类的时候,如引用很多第三方的库 - OutOfMemoryError: Request array size exceeds VM limit
原因:创建的数组大于堆内存的空间 - OutOfMemoryError: request <size> vytes for <reason>. Out of swap space?
原因:本地分配失败。JNI、本地库或者Java虚拟机都会从本地堆中分配内存空间 - OutOfMemoryError:<reason> <stack trace>(Native method)
原因:本地方法内存分配失败,JNI或本地方法或JVM发现
对象分配规则
- 对象优先分配在Eden区,若Eden没有足够空间时,JVM执行一次Minor
- 大对象直接进入老年代(大对象指大量连续内存空间的对象),避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)
- 长期存活的对象进入老年代
JVM为每个对象定义一个年龄计数器,对象经过1次Minor GC,对象进入Survivor区,之后每经过一次Minoor GC那么对象年龄加1,达到阀值(15)进入老年区 - 动态判断对象的年龄
若Survivor区中相同年龄的所有对象大小总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可直接进入老年代 -
空间分配担保
每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,若此值大于老年区的剩余值大小则进行一次Full GC,若小于检查HandlePromotionFailure设置,若true则只进行Monitor GC,若false则进行Full GC
空间分配担保.png