notebooks
  • notebooks
  • _planning
    • 2022 OKR
    • basketball
    • swimming
  • communication
    • Dubbo
    • Kafka
    • Messaging
    • RPC
    • Thrift
  • computation
    • map-reduce
  • cs-basic-knowledge
    • computer-architecture
    • data-structure-and-algorithms
    • networks
    • os
  • devops
    • Docker
    • Linux
    • Prometheus
    • operations
    • security
    • trouble-shooting
  • distributed-knowledge
    • Zookeeper_CMD
    • distributed-system
  • game-engine
    • Unity
  • others
    • appium使用
  • protocols
    • http(s)协议
    • 官方链接
    • sip
  • storage
    • Elasticsearch
    • GuavaCache
    • MySQL
    • MySQL_CMD
    • NoSQL
    • Redis
    • Redis_CMD
  • system-design
    • system-design
  • tools
    • Git
    • IDEA
    • Mac
    • VScode
    • Vim
  • _working
    • doc-template
      • backend-design-review
      • correction-of-error
      • service-review
    • process
      • domain-backup
      • oncall
  • blogs
    • history
      • 8088/8086微处理器
      • 8088/8086指令系统
      • CSS-DOM
      • CSS定位
      • CSS工作原理
      • CSS控制背景
      • CSS浮动布局
      • CSS盒模型
      • Chrome开发者工具使用方法
      • DOM
      • Django Model模型层学习
      • Django-REST-framework Serializers学习
      • Django-REST-framework Views和ViewSets学习
      • Django View视图层学习
      • Gvim下Emmet安装及使用教程
      • HTTP协议简介
      • HashMap原理初探
      • JavaScript简史
      • JavaScript语法
      • Java内存模型和GC机制
      • Java基础——Lambda学习
      • Java基础——方法引用
      • Java基础——枚举类型
      • Java类加载机制
      • KMP算法
      • Kafka学习
      • Linux下用命令行编译Java程序
      • MathJax简介和基本用法
      • Python实现常见数据结构
      • Python装饰器总结
      • TCP协议的三次握手和四次挥手
      • Thrift学习
      • asyncio学习
      • markdown的常用语法
      • 修改hosts文件实现翻墙
      • 充实文档的内容
      • 关系数据库
      • 关系数据库标准语言SQL(一)
      • 关系数据库标准语言SQL(二)
      • 关系数据理论
      • 关系查询处理和查询优化
      • 内联元素和块级元素
      • 剑指offer算法题练习
      • 动态创建标记
      • 图形化用户界面
      • 在Eclipse中使用Maven构建Java Web项目
      • 增加微博秀遇到的一些问题
      • 处理机调度
      • 如何用github和hexo搭建个人博客
      • 存储管理
      • 存储系统的层次结构
      • 学习模仿lionhit网站首页的过程总结
      • 实用的GitHub小技巧
      • 并发控制
      • 循环与分支程序设计
      • 指令系统的设计
      • 指令级并行及其开发——硬件方法
      • 搭建自己的VPN服务器
      • 操作系统用户界面
      • 数据库安全性
      • 数据库完整性
      • 数据库恢复技术
      • 数据库绪论
      • 数据库编程
      • 数据库设计
      • 数据抽象
      • 文件系统
      • 文法和语言
      • 最佳实践
      • 案例研究:JavaScript图片库
      • 案例研究:图片库改进版
      • 汇编语言程序格式
      • 汇编语言程序设计基础知识
      • 流水线技术
      • 深度优先搜索和广度优先搜索
      • 牛客网——网易2017秋招编程题集合
      • 用JavaScript实现动画效果
      • 第一篇博客
      • 经典排序算法总结(Java实现)
      • 经典查找算法总结(Java实现)
      • 综合示例
      • 编译原理引论
      • 背包、队列和栈
      • 虚拟机安装Linux系统及常用软件
      • 计算机操作系统绪论
      • 计算机系统结构的基础知识
      • 设备管理
      • 设计模式之代理模式
      • 设计模式之单例模式
      • 设计模式之工厂模式
      • 设计模式之策略模式
      • 设计模式之观察者模式
      • 词法分析
      • 进程管理
      • 闭包
      • 阻止Google自动跳转到香港服务器的方法
      • 项目部署过程
  • programming-language
    • C#
      • C#
    • C&C++
      • C
    • C&C++
      • C++
    • Java
      • GoogleGuice
    • Java
      • JVM
    • Java
      • Java
    • Java
      • Maven
    • Java
      • Mybatis
    • Java
      • Spring知识
    • Java
      • SpringBoot
    • Java
      • Tomcat
    • Python
      • Python
    • Shell
      • Shell
  • wheels
    • dcc
      • 产品调研
      • 方案设计
    • red-envelope
      • 方案设计
    • short-url
      • 短链接服务
    • sso
      • 方案设计
Powered by GitBook
On this page
  • 内存溢出原因
  • 元数据区
  • GC垃圾收集器
  • GC垃圾回收算法
  • GC Roots 对象
  • GC 触发条件
  • JVM 类加载
  • JDK命令行工具
  • Full GC 排查
  • JVM分析
  • 查看JVM启动参数
  • Dump堆栈
  • 分析dump文件
  • JVM参数含义
  1. programming-language
  2. Java

JVM

PreviousJavaNextJava

Last updated 2 years ago

内存溢出原因

1.不断的new对象

2.对象过大,超过堆大小

3.内存泄漏,导致内存无法回收

4.永久代内存溢出:Class过多

元数据区

在 Java8 中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间 的本质和永久代类似,元空间与永久代之间最大的区别在于: 元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由 MaxPermSize 控制, 而由系统的实际可用空间来控制。

GC垃圾收集器

image

新生代

  1. Serial 垃圾收集器(单线程、复制算法):是 java 虚拟机运行在 Client 模式下默认的新生代垃圾收集器。

  2. ParNew垃圾收集器(Serial+多线程):是很多 java 虚拟机运行在 Server 模式下新生代的默认垃圾收集器。

  3. Parallel Scavenge 收集器(多线程复制算法、高效):吞吐量。

老年代

  1. SerialOld收集器(单线程标记整理算法):运行在 Client 默认的 java 虚拟机默认的年老代垃圾收集器。

  2. ParallelOld收集器(多线程标记整理算法)

  3. CMS收集器(多线程标记清除算法):主要目标是获取最短垃圾回收停顿时间。会产生内存碎片。

  4. G1收集器:可预测的停顿,减少内存碎片

CMS垃圾收集器 通过参数-XX:+UseConcMarkSweepGC进行设置。 回收过程:

  1. 初始标记(CMS-initial-mark),仅标记GC Roots能直接关联到的对象,会导致stw;

  2. 并发标记(CMS-concurrent-mark),与用户线程同时运行,进行GC Roots Tracing,从“初始标记”阶段标记的对象开始找出所有存活的对象;

  3. 重新标记(CMS-remark),为了修正并发标记期间因程序继续运行而导致标记产生变动的那一部分对象的标记记录,会导致stw;

  4. 并发清除(CMS-concurrent-sweep),与用户线程同时运行;

G1垃圾收集器 TODO

G1为什么可预测停顿时间

G1有一个步骤是让使用者自行设置暂停应用的时间,因为G1回收的第4步,它是“选择一些内存块”,而不是整代内存来回收,其它GC每次回收都会回收整个内存(Eden, Old),回收内存所需的时间取决于内存的大小,以及实际垃圾的多少,所以垃圾回收时间是不可控的;而G1每次并不会回收整代内存,到底回收多少内存就看用户配置的暂停时间,配置的时间短就少回收点相反就多回收点。G1垃圾收集器的G1其实是Garbage First的意思,这里并不是垃圾优先的意思,而是优先处理那些垃圾多的内存块的意思。

CMS 与 G1 区别

  1. CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;

  2. CMS收集器以小的停顿时间为目标的收集器;G1收集器可预测垃圾回收的停顿时间;

  3. CMS收集器是使用“标记-清除”算 法进行的垃圾回收,容易产生内存碎片G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片;

  4. CMS是整代一起收集;G1不是整代收集,而是收集那些垃圾多的内存块。

GC垃圾回收算法

引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。

无法处理循环引用。

根搜索法

设立若干种根对象,当任何一个根对象到某一个对象均不可达时,则认为这个对象是可以被回收的。

从根(GC Roots)的对象作为起始点,开始向下搜索,搜索所走过的路径称为“引用链”,当一个对象到GC Roots没有任何引用链相连(用图论的概念来讲,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

复制算法(基于根搜索法)

将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。

缺点:空间的浪费

现代虚拟机将内存分为一块比较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。

当Survivor空间不够用时,需要依赖于老年代进行分配担保,所以大对象直接进入老年代。

标记-清除算法(基于根搜索法)

标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象;然后,在清除阶段,清除所有未被标记的对象。

缺点:

  • 效率比较低(递归与全堆对象遍历)

  • 空闲内存是不连续的

标记-整理算法(基于根搜索法)

和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记;但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端;之后,清理边界外所有的空间。

标记:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。

整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。

GC Roots 对象

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象

  2. 方法区中类静态属性引用的对象

  3. 方法区中常量引用的对象

  4. 本地方法栈中JNI(即native方法)引用的对象

GC 触发条件

Young GC 触发条件

  • 对象优先在新生代Eden区中分配,如果Eden区没有足够的空间时,就会触发一次young gc

Full GC 触发条件

  • 在执行Young gc之前,JVM会进行空间分配担保——如果老年代的连续空间小于新生代对象的总大小(或历次晋升的平均大小),则触发一次full gc

  • 显式调用System.gc()方法时(此方法的调用是建议JVM进行Full GC)

  • 大对象直接进入老年代,从年轻代晋升上来的老对象,尝试在老年代分配内存时,但是老年代内存空间不够

  • 方法区空间不足,如加载的类、反射的类和调用的方法较多时可能造成方法区空间不足

JVM 类加载

JVM类加载器

1.启动类加载器(Bootstrap ClassLoader)

负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath 参数指定路径中的,且被虚拟机认可(按文件名识别,如 rt.jar)的类。

2.扩展类加载器(Extension ClassLoader)

负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs 系统变量指定路径中的类库。

3.应用程序类加载器(Application ClassLoader)

负责加载用户路径(classpath)上的类库。

FQA: 为什么不能合并成一个类加载器?

每一个类加载器都是为了去在不同的情景下去加载类。加载类的方法有无数种,需要一个灵活的加载器系统去在特定的情况下来加载类,如从本地系统、网络、源文件、jar包中加载类。

类加载器可以在 load class 时对 class 进行重写和覆盖,在此期间就可以对类进行功能性的增强。

JVM类加载机制

JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化。

1.加载

加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个 Class 文件获取,这里既可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将 JSP 文件转换成对应的 Class 类)。

2.验证

确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。有文件格式验证、元数据验证(语义分析,类与类的继承关系等)、字节码验证(数据流和控制流分析)、符号引用验证(对类自身以外的信息进行匹配校验)。

3.准备

准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。

4.解析

虚拟机将常量池中的符号引用替换为直接引用的过程。

5.初始化

初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。

哪些类会被加载

TODO

类卸载

类卸载条件

  • 该类所有的实例都已经被GC。

  • 加载该类的ClassLoader实例已经被GC。

  • 该类的java.lang.Class对象没有在任何地方被引用。

双亲委派模型

某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

意义:

  1. 避免重复加载

  2. 保证java核心库安全的性

遇到的问题

1.类延迟加载导致代码部署后未立刻执行

static {
    pool.scheduleAtFixedRate(() -> {
        try {
            // 刷新配置
            configs = refreshConfigs();
        } catch (Exception e) {
            log.error("xxx", e);
        }
    }, 10, 120, TimeUnit.SECONDS);
}

static代码块延迟加载导致 静态变量configs 没有在第一时间初始化,等到所在的类被引用时才会初始化并启动定时任务。

JDK命令行工具

jmap,jstack,jstat,jinfo,jps TODO

Full GC 排查

可能原因

  1. 程序执行了System.gc()

  2. 执行了jmap -histo:live pid命令,这个会立即触发fullgc

  3. 在执行Minor GC的时候进行的一系列检查 a.执行Minor GC的时候,JVM会检查老年代中最大连续可用空间是否大于了当前新生代所有对象的总大小 b.如果大于,则直接执行Minor GC(这个时候执行是没有风险的) c.如果小于了,JVM会检查是否开启了空间分配担保机制,如果没有开启则直接改为执行Full GC d.如果开启了,则JVM会检查老年代中最大连续可用空间是否大于了历次晋升到老年代中的平均大小,如果小于则执行改为执行Full GC e.如果大于则会执行Minor GC,如果Minor GC执行失败则会执行Full GC

  4. 使用了大对象,大对象会直接进入老年代

  5. 在程序中长期持有了对象的引用,对象年龄达到指定阈值也会进入老年代

  6. 程序中存在内存泄露,导致老年代内存缓慢增长,且无法被回收,达到了GC阈值

排查步骤

  1. 通过JVM参数获取dump文件(-XX:HeapDumpBeforeFullGC)

  2. 通过JDK自带的工具jmap获取dump文件

  3. 通过JDK自带的jvisualvm工具分析或者下载三方软件jprofiler来分析dump文件即可

  4. 最重要的一点,要把fullgc发生时刻的dump文件和正常没有发生fullgc时间的dump文件都下载到本地,然后对比观察分析方便找到问题的原因

JVM分析

查看JVM启动参数

1.jcmd {pid} VM.flags

2.jinfo -flags {pid} 或 jmap -heap {pid}

3.jps -v 或 ps -ef | grep java

Dump堆栈

  1. -XX:+HeapDumpOnOutOfMemoryError 当堆内存OOM时自动dump堆内存

  2. 通过JMX的MBean生成当前的堆栈信息(可用于JVM监控)

  3. jmap -dump:live,format=b,file=heap.hprof {pid} 或 jcmd {pid} GC.heap_dump {file-path} 例如,jcmd 2523 GC.heap_dump ~/Desktop/heap.hprof

  4. jstack {pid} > {file-path} 或 jcmd {pid} Thread.print > {file-path} 例如,jstack 2523 > ~/Desktop/threaddump.txt

分析dump文件

  • 堆dump文件 -> MAT, VisualVM, IDEA Profiler

  • 栈dump文件 -> https://gceasy.io/

JVM参数含义

  • -XX:MetaspaceSize=128m (元空间默认大小)

  • -XX:MaxMetaspaceSize=128m (元空间最大大小)

  • -Xms1024m (堆默认大小)

  • -Xmx1024m (堆最大大小)

  • -Xmn256m (年轻代大小,建议不要太大,太大可能会是YGC时间过长,影响吞吐量)

  • -XX:MaxDirectMemorySize=64m (堆外内存大小)

  • -Xss256k (栈最大深度大小)

  • -XX:+UseConcMarkSweepGC (指定使用的垃圾收集器,这里使用CMS收集器)

  • -XX:+UseParNewGC (年轻代收集器)

  • -XX:+PrintGCDetails (打印详细的GC日志)

  • -XX:+PrintGCTimeStamps (打印gc时间戳)

  • -Xloggc:/appl/gc-%t.log (定义gc日志目录,建议加时间而不是-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=100)

  • -XX:+HeapDumpOnOutOfMemoryError (当JVM发生OOM时,自动生成DUMP文件)

  • -XX:HeapDumpPath=./logs (生成DUMP文件的路径)