JVM

9/1/2022 JVM

# JVM内存结构(运行时数据区域)

# 线程共享

# 方法区(JVM的规范)

# JDK8,永久代->元空间
# 用来存放已被虚拟机加载的类相关信息
# 类信息

类的版本,字段,方法,接口,父类

# 常量池

静态常量池(字面量,符号引用)

运行时常量池(直接引用)

直接指向目标的指针

相对偏移量

一个能间接定位到目标的句柄

-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)

-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小

#

虚拟机所管理的内存中最大的一块,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存

# 新生代内存(Young Generation)
# Eden

大部分情况,对象都会首先在 Eden 区域分配,进入 S0 或者 S1后初始年龄变为 1

# Survivor0
# Survivor1
# 老生代(Old Generation)

年龄增加到一定程度(默认为 15 岁),晋升到老年代中,可以通过参数 -XX:MaxTenuringThreshold设置

# 永久代(Permanent Generation)

JDK 8 版本之后已被 Metaspace(元空间) 取代,元空间使用的是直接内存

# JDK7之后新加部分,之前在永久代

永久代的 GC 回收效率太低,只有在整堆收集 (Full GC)的时候才会被执行 GC

# 静态变量
# 字符串常量池

堆这里最容易出现的就是 OutOfMemoryError 错误

GC Overhead Limit Exceeded

当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时

Java heap space

堆内存中的空间不足以存放新创建的对象,和配置的最大堆内存有关,-Xmx参数

# 直接内存

非运行时数据区的一部分

# 线程私有

# 虚拟机栈

每个线程在创建的时候都会创建一个,用于管理 Java 函数的调用,StackOverFlowError和OutOfMemoryError

# 栈帧

每次方法调用都会创建一个,return 语句和抛出异常导致方法返回

局部变量表

操作数栈

动态链接

方法返回值

# 异常

StackOverFlowError

内存大小不允许动态扩展,当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度

OutOfMemoryError

内存大小可以动态扩展,在动态扩展栈时无法申请到足够的内存空间

# 本地方法栈

用于管理本地方法的调用

# 程序计数器

用于记录各个线程执行的字节码的地址

程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡

# 对象

# 对象创建

# 类加载检查

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用

# 分配内存

# 指针碰撞

适用场合:堆内存规整

原理:用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界值指针,向没用的地方移动指针

GC收集器:Serial,ParNew

# 空闲列表

适用场合:堆内存不规整的情况下

原理:虚拟机会维护一个列表,该列表记录哪些块可用

GC收集器:CMS

# 并发问题
# CAS+失败重试

假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止

# TLAB

首先在 TLAB 分配

# 初始化零值

将分配到的内存空间都初始化为零值(不包括对象头)

# 设置对象头

这个对象是哪个类的实例、

如何才能找到类的元数据信息、

对象的哈希码、

对象的 GC 分代年龄等信息

# 执行 init 方法

执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化

# 对象的内存布局

# 对象头

# 类型指针

虚拟机通过这个指针来确定这个对象是哪个类的实例

# 用于存储对象自身的运行时数据
# 哈希码
# GC 分代年龄
# 锁状态标志

# 实例数据

对象真正存储的有效信息

# 对齐填充

对象的大小必须是 8 字节的整数倍

# 对象的访问

# 使用句柄

Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,句柄中包含了对象实例数据与类型数据各自的具体地址信息

# 直接指针

reference 中存储的直接就是对象的地址

#

# 加载过程

加载->连接(验证,准备,解析)->初始化->使用->卸载

JDK 7 及之后,把原本放在永久代的字符串常量池、静态变量等移动到堆中

# 加载器

# BootstrapClassLoader(启动类加载器)

由 C++实现,负责加载 %JAVA_HOME%/lib目录下的 jar 包和类或者被 -Xbootclasspath参数指定的路径中的所有类

JRE的lib目录下的核心类库

# ExtensionClassLoader(扩展类加载器)

父类加载器为 null,null 并不代表没有,而是 BootstrapClassLoader

继承自java.lang.ClassLoader,加载 %JRE_HOME%/lib/ext 目录下的 jar 包和类,或被 java.ext.dirs 系统变量所指定的路径下的 jar 包

JRE的lib目录下的ext扩展目录中的jar包

# AppClassLoader(应用程序类加载器)

继承自java.lang.ClassLoader,加载当前应用 classpath 下的所有 jar 包和类

加载你自己写的那些类

# UserDefinedClassLoader(用户自定义类加载)

继承自java.lang.ClassLoader,

# Tomcat类加载

# BootstrapClassLoader

# ExtensionClassLoader

# AppClassLoader

# CommonClassLoader

# Catalina ClassLoader

⽤于加载服务器内部可⻅类,这些类应⽤程序不能访问

# SharedClassLoader

⽤于加载应⽤程序共享类,这些类服务器不会依赖

# WebappClassLoader

每个应⽤程序都会有⼀个独⼀⽆⼆的Webapp ClassLoader,他⽤来加载本应⽤程序 /WEB-INF/classes 和 /WEB-INF/lib 下的类

# Tomcat

# 热加载

即在在运行时重新加载class

如果发现有文件发生变化,热加载开关开启

关闭Context容器

重启Context容器

# 热部署

在服务器运行时重新部署项目

检查Host管理下的所有web应用

如果原来的Web应用被删除,就将相应Context容器删除

如果有新War包放进来,就部署相应的War包

# 双亲委派

保证了 Java 程序的稳定运行,可以避免类的重复加载,也保证了 Java 的核心 API 不被篡改

# 打破双亲委派

自定义加载器的话,需要继承 ClassLoader,重写 loadClass() 方法

不想打破,就重写 ClassLoader 类中的 findClass() 方法

# 垃圾回收

# 垃圾收集算法(方法论)

# 标记-清除算法

标记出所有不需要回收的对象,统一回收掉所有没有被标记的对象,最基础的收集算法

1.效率问题2.空间问题(标记清除后会产生大量不连续的碎片)

# 标记-复制算法

当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉

每次的内存回收都是对内存区间的一半进行回收,解决效率问题

# 标记-整理算法

让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存

# 分代收集算法

在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集

老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集

# 垃圾收集器(具体实现)

# Serial收集器

最基本、历史最悠久,单线程,它在进行垃圾收集工作的时候必须暂停其他所有的工作线程(STW),简单而高效

新生代标记复制,老年代标记整理

# ParNew 收集器

Serial 收集器的多线程版本,许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器配合工作

# Parallel Scavenge 收集器( JDK1.8 默认收集器)

Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)

CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)

# Serial Old 收集器

Serial 收集器的老年代版本

在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用

作为 CMS 收集器的后备方案

# Parallel Old 收集器( JDK1.8 默认收集器)

Parallel Scavenge 收集器的老年代版本

Parallel Old 收集器( JDK1.8 默认收集器)

# CMS 收集器

HotSpot 虚拟机第一款真正意义上的并发收集器,第一次实现了让垃圾收集线程与用户线程(基本上)同时工作

获取最短回收停顿时间为目标的收集器

# 标记-清除算法
# 初始标记

会让系统停止工作,进入STW,不过过程很短

# 并发标记

恢复系统正常运行,垃圾回收与系统是并行进行

最耗时的

# 重新标记

部分对象已经失去引用变成垃圾对象没有来得及更正,以及新创建的对象还未来得及标记

会暂停我们的系统线程,速度很快

# 并发清除

不需要移动存活对象, 所以这个阶段也是可以与用户线程同时并发的

# 优点:

并发收集、低停顿

# 缺点:

对 CPU 资源敏感;无法处理浮动垃圾;会导致收集结束时会有大量空间碎片产生

# G1 收集器

面向服务器的垃圾收集器,面向服务器的垃圾收集器,以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.

在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region

# 并行与并发

使用多个 CPU来缩短 Stop-The-World 停顿时间

# 分代收集

不需要其他收集器配合

# 空间整合

整体来看是基于“标记-整理”算法,局部上来看是基于“标记-复制”算法

# 可预测的停顿

降低停顿时间是 G1 和 CMS 共同的关注点,G1 还能建立可预测的停顿时间模型

# ZGC 收集器

标记-复制算法,出现 Stop The World 的情况会更少

# JVM参数

# 类别

标准参数(-)

非标准参数(-X)

非Stable参数(-XX)

# 堆内存

整个堆大小=年轻代 + 年老代+ 持久代,持久代一般固定大小为64m

# -XX:MaxTenuringThreshold:

设置垃圾最大年龄

# -Xms:初始堆空间

# -Xmx:设置最大堆空间

# Minor GC

# -Xmn:设置新生代堆大小
# -XX:NewSize

设置年轻代大小

# -XX:MaxNewSize
# -XX:NewRatio:

老/新

设置新生代(包括Eden和两个Survivor区)与老年代的比值为

# -XX:SurvivorRatio:

3:1:1

年轻代中Eden区与两个Survivor区的比值

# Young Generation(新生代)

Eden

From Survivor0

To Survivor1

# Major GC

# Old Generation(老年代)

Old Memory

# 方法区

# 1.8之前

-XX:PermSize

-XX:MaxPermSize,设置持久代大小

# 1.8

-XX:MetaspaceSize=N

-XX:MaxMetaspaceSize=N

# 垃圾收集

# -XX:+UseSerialGC

串行垃圾收集器

只适用于小数据量,JDK5.0以前都是使用串行收集器

# -XX:+UseParallelGC

并行垃圾收集器

# -XX:+UseParNewGC

CMS垃圾收集器

# -XX:+UseG1GC

G1垃圾收集器

# -Xss128k:

设置每个线程的栈大小为128k

JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K

# JVM调优指令

# jps

-l 输出jar包路径,类全名

-m 输出main参数

-v 输出JVM参数

# jinfo

查看JVM参数

-flags

# jstat

查看JVM运行时的状态信息,包括内存状态、垃圾回收

# jstack

查看JVM线程快照,jstack命令可以定位线程出现长时间卡顿的原因,,例如死锁,死循环

# jmap

可以用来查看内存信息

# 内存溢出

# java.lang.outofmemoryerror: java heap space

# 当堆内存不足,并且已经达到jvm设置的最大值,无法继续申请新的内存,存活的对象在堆内存中无法被回收

查看当前jvm的堆内存配置是否太小,可以考虑增加堆内存大小

-xms和-xmx最好设置相同的内存大小,可以防止因为jvm频繁进行内存的调整影响稳定性和使用

# 当一次从数据库查询大量数据,堆内存没有足够的内存可以存放大量的数据

通过jvm参数:-xx:+heapdumponoutofmemoryerror可以让虚拟机在出现内存溢出的时候dump出当前的堆内存快照

# 大量的强引用对象在堆内存中存活,gc无法回收这些对象,新创建的对象在新生代无法进行分配,full gc仍然无法进行回收

# java.lang.outofmemoryerror:stackoverflow error

# stackoverflowerror

线程请求的栈深度大于虚拟机允许的最大深度

检查代码是否出现深度递归的情况,或者递归的终止条件没有设置

# 虚拟机在扩展栈时无法申请到足够的内存空间

如果是线程的栈内存空间过小,则通过-xss设置每个线程的栈内存空间

默认的-xss参数的大小应该是1m

# direct buffer memory

directbytebuffer 的默认大小为 64 mb,超出限制抛错,使用nio则可能会出现该异常

directmemory的内存大小可以通过-xx:maxdirectmemorysize指定

# metaspace

元空间中存储的是类信息、常量池、方法描述等信息,直接使用本地内存

可以通过jvm参数来设置分配的内存空间的大小-xx:maxmetaspacesize配置参数

# 内存泄漏

# 静态集合类

长生命周期的对象持有短生命周期对象的引用

# 各种连接,如数据库连接、网络连接和IO连接等

# 变量不合理的作用域

# 内部类持有外部类

# 改变哈希值

# 栈先增长,在收缩,那么从栈中弹出的对象将不会被当作垃圾回收

# 缓存泄漏

可以使用WeakHashMap代表缓存,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值

# 监听器和回调

Last Updated: 4/15/2023, 1:36:11 AM