- [前言](#前言) - [核心知识](#核心知识) - [JVM体系结构](#jvm体系结构) - [JVM各个模块简介](#jvm各个模块简介) - [JVM是如何工作的?](#jvm是如何工作的) - [1. 运行时数据区域](#1-运行时数据区域) - [1. 程序计数器(线程私有)](#1-程序计数器线程私有) - [2. 虚拟机栈(线程私有)](#2-虚拟机栈线程私有) - [3. 本地方法栈(线程私有)](#3-本地方法栈线程私有) - [4. 堆](#4-堆) - [新生代 (Young Generation)](#新生代-young-generation) - [老年代 (Old Generation)](#老年代-old-generation) - [永久代 (Permanent Generation)](#永久代-permanent-generation) - [5. 方法区](#5-方法区) - [6. 运行时常量池](#6-运行时常量池) - [7. 直接内存](#7-直接内存) - [2. 判断一个对象是否可被回收](#2-判断一个对象是否可被回收) - [1. 引用计数算法](#1-引用计数算法) - [2. 可达性分析算法](#2-可达性分析算法) - [★ GC用的引用可达性分析算法中,哪些对象可作为GC Roots对象?](#★-gc用的引用可达性分析算法中哪些对象可作为gc-roots对象) - [3. 引用类型](#3-引用类型) - [1. 强引用(Strong Reference)](#1-强引用strong-reference) - [2. 软引用(Soft Reference)](#2-软引用soft-reference) - [3. 弱引用(Weak Reference)](#3-弱引用weak-reference) - [4. 虚引用(Phantom Reference)](#4-虚引用phantom-reference) - [4. 方法区的回收](#4-方法区的回收) - [5. finalize()](#5-finalize) - [3. 垃圾收集算法(垃圾处理方法)](#3-垃圾收集算法垃圾处理方法) - [1. 标记 - 清除](#1-标记---清除) - [2. 标记 - 整理](#2-标记---整理) - [3. 复制回收](#3-复制回收) - [★ 分代收集](#★-分代收集) - [4. 垃圾收集器](#4-垃圾收集器) - [1. Serial](#1-serial) - [2. ParNew](#2-parnew) - [3. Parallel Scavenge](#3-parallel-scavenge) - [4. Serial Old](#4-serial-old) - [5. Parallel Old](#5-parallel-old) - [6. CMS](#6-cms) - [7. G1](#7-g1) - [8. 比较](#8-比较) - [5. 内存分配与回收策略](#5-内存分配与回收策略) - [1. 什么时候进行Minor GC,Full GC](#1-什么时候进行minor-gcfull-gc) - [2. 内存分配策略](#2-内存分配策略) - [1. 对象优先在 Eden 分配](#1-对象优先在-eden-分配) - [2. 大对象直接进入老年代](#2-大对象直接进入老年代) - [3. 长期存活的对象进入老年代](#3-长期存活的对象进入老年代) - [4. 动态对象年龄判定](#4-动态对象年龄判定) - [5. 空间分配担保](#5-空间分配担保) - [3. Full GC 的触发条件](#3-full-gc-的触发条件) - [1. 调用 System.gc()](#1-调用-systemgc) - [2. 老年代空间不足](#2-老年代空间不足) - [3. 空间分配担保失败](#3-空间分配担保失败) - [4. JDK 1.7 及以前的永久代空间不足](#4-jdk-17-及以前的永久代空间不足) - [5. Concurrent Mode Failure](#5-concurrent-mode-failure) - [6. 类加载机制](#6-类加载机制) - [类的生命周期](#类的生命周期) - [类初始化时机](#类初始化时机) - [1. 主动引用](#1-主动引用) - [2. 被动引用](#2-被动引用) - [类加载过程](#类加载过程) - [1. 加载](#1-加载) - [2. 验证](#2-验证) - [3. 准备](#3-准备) - [4. 解析](#4-解析) - [5. 初始化](#5-初始化) - [类加载器](#类加载器) - [1. 类与类加载器](#1-类与类加载器) - [2. 类加载器分类](#2-类加载器分类) - [3. 双亲委派模型](#3-双亲委派模型) - [7. Student s = new Student(); 在内存中做了哪些事情](#7-student-s--new-student-在内存中做了哪些事情) - [8. Java虚拟机工具](#8-java虚拟机工具) - [(1)jps](#1jps) - [(2)jstat](#2jstat) - [(3)jinfo](#3jinfo) - [(4)jmap](#4jmap) - [(5)jhat](#5jhat) - [(6)jstack](#6jstack) - [(7)jconsole](#7jconsole) - [(8)jvisualvm](#8jvisualvm) - [9. 了解过JVM调优没,基本思路是什么](#9-了解过jvm调优没基本思路是什么) - [10. JVM线程死锁,你该如何判断是因为什么?如果用VisualVM,dump线程信息出来,会有哪些信息](#10-jvm线程死锁你该如何判断是因为什么如果用visualvmdump线程信息出来会有哪些信息) - [11. 什么是内存泄露?用什么工具可以查出内存泄漏](#11-什么是内存泄露用什么工具可以查出内存泄漏) - [* 虚拟机参数](#-虚拟机参数) - [附录:参考资料](#附录参考资料) - [更新说明](#更新说明) # 前言 在本文将深入讨论 Java 虚拟机相关核心知识 参考书籍: - 《深入理解 Java 虚拟机》周志明,机械工业出版社 学习课程: - 【炼数成金】深入 JVM 内核—原理、诊断与优化 - 【龙果学院】深入理解 Java 虚拟机( JVM 性能调优+内存模型+虚拟机原理) - 【尚学堂】白鹤翔 JVM 虚拟机优化 # 核心知识 - JVM 基本结构 - 类加载器 - 执行引擎 - 运行时数据区 - 本地接口 Class Files -> ClassLoader -> 运行时数据区 -> 执行引擎,本地库接口 -> 本地方法库 ## JVM体系结构 虚拟机是物理机器的软件实现。Java 的开发遵循 write once run anywhere(“一次编写到处乱跑”)理念,它运行在 VM(虚拟机)上。编译器将 Java 文件编译成 Java.class 文件,之后,将 .class 文件输入到 JVM 中,加载并执行该类文件。下图是 JVM 的体系结构  ### JVM各个模块简介 1. 运行时数据区:经过编译生成的字节码文件(class文件),由 class loader(类加载子系统)加载后交给执行引擎执行。在执行引擎执行的过程中产生的数据会存储在一块内存区域。这块内存区域就是运行时区域 2. 程序计数器:用于记录当前线程的正在执行的字节码指令位置。由于虚拟机的多线程是切换线程并分配 cpu 执行时间的方式实现的,不同线程的执行位置都需要记录下来,因此程序计数器是线程私有的 3. 虚拟机栈:虚拟机栈是 Java 方法执行的内存结构,虚拟机会在每个 Java 方法执行时创建一个“栈桢”,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。当方法执行完毕时,该栈桢会从虚拟机栈中出栈。其中局部变量表包含基本数据类型和对象引用 - 在 Java 虚拟机规范中,对这个区域规定了两种异常状态:如果线程请求的栈的深度大于虚拟机允许的深度,将抛出 StackOverFlowError 异常(栈溢出),如果虚拟机栈可以动态扩展(现在大部分 Java 虚拟机都可以动态扩展,只不过 Java 虚拟机规范中也允许固定长度的 Java 虚拟机栈),如果扩展时无法申请到足够的内存空间,就会抛出 OutOfmMemoryError 异常(没有足够的内存) 4. 本地方法栈:类似 Java 方法的执行有虚拟机栈,本地方法的执行则对应有本地方法栈 5. 方法区:用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。线程共享(看存储的数据就知道了) 6. Java 堆(Heap):堆的主要作用是存放程序运行过程中创建的对象实例,因为要存放的对象实例有可能会极多,因此也是虚拟机内存管理中最大的一块。并且由于硬件条件有限,所以需要不断回收已“无用”的实例对象来腾出空间给新生成的实例对象;因此 Java 的垃圾回收主要是针对堆进行回收的(还有方法区的常量池),Java 堆很多时候也被称为GC堆(Garbage Collected Heap)。 7. 类加载机制(Class Loader):类加载子系统是根据一个类的全限定名来加载该类的二进制流到内存中,在JVM 中将形成一份描述 Class 结构的元信息对象(方法区),通过该元信息对象可以获知 Class 的结构信息:如构造函数,属性和方法等,Java 允许用户借由这个 Class 相关的元信息对象间接调用 Class 对象的功能。 ### JVM是如何工作的? 如上面的体系结构图所示,JVM 分为三个主要的子系统: 1. 类加载器子系统 2. 运行时数据区 3. 执行引擎 **1、类加载器子系统** Java的动态类加载功能是由类加载器子系统处理的。它负责加载、链接,并且在**运行时**首次引用类的时候初始化类,而不是在编译期间。 **1.1、加载** 这个组件负责加载类。BootStrap类加载器、Extension类加载器和Application类加载器是实现这个功能的三大类加载器。 1. **BootStrap类加载器** —— 负责从classpath加载类,如果没有类存在,将只加载**rt.jar**。这个加载器的优先级最高。 2. **Extension类加载器** —— 负责加载**扩展文件夹(jre\lib)**中的类。 3. **Application类加载器** —— 负责加载**应用级classpath**和环境变量指向的路径下的类。 上述**类加载器**在加载类文件时遵循**委托层次结构算法**。 **1.2、链接** 1. **校验** —— 字节码验证器将校验生成的字节码是否正确,如果校验失败,我们将获得**校验错误信息**。 2. **准备** —— 对于所有的静态变量,内存将被申请并分配默认值。 3. **解析** —— 所有**标记的内存引用**从**方法区域**被替换成的**原始引用**。 **1.3、初始化** 这是类加载的最后阶段,所有的静态变量都将被分配原值,静态代码块将被执行。 运行时数据区 运行时数据区被划分为五个主要部分: - **方法区** —— 所有**类级数据**都将存储在这里,包括**静态变量**。每一个JVM只有一个方法区,并且它是一个共享资源。 - **堆区** —— 所有**对象**及其对应的**实例变量**和**数组**等存储在此,每个JVM同样只有一个堆区。由于**方法区**和**堆区**是多线程内存共享,因此存储的数据是非线程安全的。 - **栈区** —— 每个线程都会创建一个单独的**运行时栈**。在每一次**方法调用**,都会在栈内存中创建一个**栈帧(Stack Frame)**。所有**局部变量**将在栈内存中创建。栈区是线程安全的,因为它不是一个共享资源。栈帧可以被划分为三个实体: > **局部变量数组** —— 与方法中有多少局部变量有关,相应的值将存储在此处。 > **操作数栈** —— 如果任何的中间操作需要被执行,**操作数栈**将作为运行时工作区来执行操作。 > **帧数据** —— 与方法相对应的所有符号存储在此。在任何异常情况下,catch块的信息被保留在帧数据中。 - **PC寄存器** —— 每一个线程都有单独的**PC寄存器**,一旦执行指令,PC寄存器将被下一条指令**更新**,保存当前**执行指令**的地址。 - **本地方法栈** —— 本地方法栈保存本地方法信息,每一个线程都会创建一个单独的本地方栈。 **3、执行引擎** 分配到运行时数据区的字节码将被执行引擎执行。执行引擎读取字节码并逐一执行。 - **解释器** —— 解释器能更加快速地解释字节码,但是执行缓慢。解释器的缺点是当多次调用一个方法时,每次都要重新解释。 - **JIT编译器** —— JIT编译器弥补了解释器的不足。执行引擎使用解释器来转换字节码,当它发现重复的代码时,它将使用JIT编译器来编译整个字节码并转换为本地代码。本地代码将直接被重复的方法所调用,从而提高系统性能。 - **中间代码生成器** —— 生成中间代码。 - **代码优化器** —— 负责优化上述生成的中间代码。 - **目标代码生成器** —— 负责生成机器码或者本地代码。 - **分析器** —— 一个特殊的组件,负责查找热点代码,比如一个方法是否被调用多次。 - **垃圾回收器** —— 回收并删除未引用的对象。可以通过调用**System.gc()**来触发垃圾回收,但不能保证它执行。JVM的垃圾回收是回收被创建的对象。 **Java本地接口(JNI)**:**JNI**与**本地方法库**交互,并为执行引擎提供**本地方法库**。 **本地方法库(Native Method Libraries)**:它是执行引擎所需的本地库集合。 ## 1. 运行时数据区域

