JVM笔记

1.JVM、JRE、JDK 的关系

JVM 是 Java 程序能够运行的核心。
JVM 标准加上实现的一大堆基础类库,就组成了 Java 的运行时环境,也就是我们常说的 JRE(Java Runtime Environment)。
对于 JDK 来说,就更庞大了一些。除了 JRE,JDK 还提供了一些非常好用的小工具,比如 javac、java、jar 等。它是 Java 开发的核心。
在这里插入图片描述

2.JVM内存结构

JVM 内存共分为:虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。
在这里插入图片描述

3.JVM模块划分

JVM分为五大模块: 类装载器子系统 、 运行时数据区 、 执行引擎 、 本地方法接口 和 垃圾收集模块

在这里插入图片描述

4. jdk1.7和jdk1.8的jvm内存结构对比

在这里插入图片描述
Java8为什么要将永久代替换成Metaspace?
1.字符串存在永久代中,容易出现性能问题和内存溢出。
2.类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
3.永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
4.Oracle 可能会将HotSpot 与 JRockit 合二为一,JRockit没有所谓的永久代。

5. PC寄存器

程序计数器(Program Counter Register):也叫PC寄存器,是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。
在这里插入图片描述

6.虚拟机栈

Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,即生命周期和线程相同。Java虚拟机栈和线程同时创建,用于存储栈帧。每个方法在执行时都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态
链接、方法出口
等信息。每一个方法从调用直到执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
在这里插入图片描述

6.1虚拟机栈——局部变量表

局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内定义的局部变量。包括8种基本数据类型、对象引用(reference类型)和returnAddress类型(指向一条字节码指令的地址)。
其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其余的数据类型只占用1个。
在这里插入图片描述

6.2.虚拟机栈——操作数栈

操作数栈(Operand Stack)也称作操作栈,是一个后入先出栈(LIFO)。随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。
在这里插入图片描述

6.3虚拟机栈——动态链接

Java虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的是为了支持方法调用过程中的动态链接(Dynamic Linking)。
在这里插入图片描述

6.4虚拟机栈——方法返回地址

方法返回地址存放调用该方法的PC寄存器的值。一个方法的结束,有两种方式:正常地执行完成,出现未处理的异常非正常的退出。无论通过哪种方式退出,在方法退出后都返回到该方法被调用的位置。方法正常退出时,调用者的PC计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。

6.5虚拟机栈——本地方法栈

本地方法栈(Native Method Stacks) 与虚拟机栈所发挥的作用是非常相似的, 其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码) 服务, 而本地方法栈则是为虚拟机使用到的本地(Native) 方法服务。
在这里插入图片描述

7.堆

7.1 java7的堆结构

在这里插入图片描述

7.2 java8的堆结构

在这里插入图片描述

7.3 年轻代垃圾回收(Minor GC)过程图解:

在这里插入图片描述
在这里插入图片描述

7.4 对象分配过程:

在这里插入图片描述

7.5 垃圾回收触发条件:

年轻代GC触发条件:

· 年轻代空间不足,就会触发Minor GC, 这里年轻代指的是Eden代满,Survivor不满不会引发GC
· Minor GC会引发STW(stop the world) ,暂停其他用户的线程,等垃圾回收接收,用户的线程才恢复.

老年代GC (Major GC)触发机制:

· 老年代空间不足时,会尝试触发MinorGC. 如果空间还是不足,则触发Major GC
· 如果Major GC , 内存仍然不足,则报错OOM

FullGC 触发机制:

· 调用System.gc() , 系统会执行Full GC ,不是立即执行.
· 老年代空间不足
· 方法区空间不足

8.方法区

在这里插入图片描述

8.1方法区中存储的内容:

类型信息(域信息、方法信息)
运行时常量池

类型信息

对每个加载的类型(类Class、接口 interface、枚举enum、注解 annotation),JVM必须在方法区中存储以下类型信息:
① 这个类型的完整有效名称(全名 = 包名.类名)
② 这个类型直接父类的完整有效名(对于 interface或是java.lang. Object,都没有父类)
③ 这个类型的修饰符( public, abstract,final的某个子集)
④ 这个类型直接接口的一个有序列表

域信息

域信息,即为类的属性,成员变量
JVM必须在方法区中保存类所有的成员变量相关信息及声明顺序。
域的相关信息包括:域名称、域类型、域修饰符(pυblic、private、protected、static、final、volatile、transient的某个子集)

方法信息

JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序:

  1. 方法名称方法的返回类型(或void)
  2. 方法参数的数量和类型(按顺序)
  3. 方法的修饰符public、private、protected、static、final、synchronized、native,、abstract的一个子集
  4. 方法的字节码bytecodes、操作数栈、局部变量表及大小( abstract和native方法除外)
  5. 异常表( abstract和 native方法除外)。每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引

方法区设置

jdk7及以前:通过-xx:Permsize来设置永久代初始分配空间、-XX:MaxPermsize来设定永久代最大可分配空间
JDK8以后:元数据区大小可以使用参数 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize指定

运行时常量池

运行时常量池是常量池在运行时的表现形式(即字节码.class文件被jvm加载到内存后,.class里面的常量池对应加载到方法区的运行时常量池)
常量池:可以看做是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型。
为什么需要常量池?
举例来说:

public class Solution {
	public void method() {
		System.out.println("are you ok");
	}
}

这段代码很简单,但是里面却使用了 String、 System、 PrintStream及Object等结构。如果代码多,引用到的结构会更多!这里就需要常暈池,将这些引用转变为符号引用,具体用到时,采取加载。

9.类的生命周期

加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initiallization)、使用(Using)和卸载(Unloading)这7个阶段。
在这里插入图片描述

加载
1.预加载:加载的是JAVA_HOME/lib/下的rt.jar下的.class文件
2.运行时加载:会先去内存中查看一下这个.class文件有没有被加载,如果没有就会按照类的全限定名来加载这个类。

加载阶段做了三件事
1.获取.class文件的二进制流
2.将类信息、静态变量、字节码、常量这些.class文件中的内容放入方法区中
3.在内存中生成一个代表这个.class文件的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

准备
为类变量分配内存并设置其初始值的阶段
解析
虚拟机将常量池内的符号引用替换为直接引用
初始化
这里是类的初始化<cinit>,是执行类构造器()方法的过程,并依次给类变量赋值(这里和准备阶段不同,准备阶段赋予的是默认初始值,比如int初始值是0。如果类变量中写了static int a=1,那么在准备阶段变量a被赋值0,初始化阶段变量a就会被赋值为1),赋值顺序是父类先子类后、从上到下。

<cinit>与<init>区别:<cinit>是类初始化方法,<init>是对象初始化方法,<cinit>最先执行,且只执行一次

10. 类加载器

1、jvm支持两种类型的加载器,分别是引导类加载器 和 自定义加载器
2、引导类加载器是由c/c++实现的,自定义加载器是由java实现的。
引导类加载器BootStrapClassLoader(启动类加载器)
自定义类加载器(Extension Class Loader(扩展类加载器)、System Class Loader或AppClassLoader(系统类加载器)、User-Defined ClassLoader(用户自定义类加载器
))

11.双亲委派模型

双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即 ClassNotFoundException ),子加载器才会尝试自己去加载。
在这里插入图片描述

为什么需要双亲委派模型:保证多加载器加载某个类时,最终都是由一个加载器加载,确保最终加载结果相同

12.垃圾回收

12.1判断对象是否存活

1.引用计数算法(Java未使用)
给每个创建的对象添加一个引用计数器,每当此对象被某个地方引用时,计数值+1,引用失效时-1,所以当计数值为0时表示对象已经不能被使用。缺点:不能解决循环依赖问题。
2.可达性分析算法
以“GC Roots”的对象作为起始点,从起始点开始向下搜索到对象的路径。搜索所经过的路径称为引用链(Reference Chain),当一个对象到任何GC Roots都没有引用链时,则表明对象“不可达”。

12.2 回收垃圾过程

1.进行可达性分析(第一次标记)
2.判断是否需要执行finalize()方法,不需要则回收,需要的话则执行
3.执行finalize()方法时,看是否有重新建立到GC Roots的引用(第二次标记),没有则回收,有则移出回收队列
4.下一次垃圾回收时,重复1.2.3步骤,但如果同一个对象之前执行过finalize()方法,则2步骤时会判断为不需要执行finalize()方法

12.3 引用分类

强引用(Strong Reference) 、 软引用(Soft Reference) 、 弱引用(Weak Reference) 和 虚引用(Phantom Reference) 四种,这四种引用的强度依次递减。

强引用:如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟
机抛出OutOfMemoryError错误
软引用:如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。
弱引用:被弱引用关联的对象只能生存到下一次垃圾收集发生为止。
虚引用:如果一个对象仅持有虚引用,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。

13.垃圾收集算法

13.1分代收集理论

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

部分收集

  • 新生代收集(Minor GC/Young GC): 指目标只是新生代的垃圾收集。
  • 老年代收集(Major GC/Old GC): 指目标只是老年代的垃圾收集,目前只有CMS收集器会有单独收集老年代的行为。
  • 混合收集(Mixed GC): 指目标是收集整个新生代以及部分老年代的垃圾收集。 目前只有G1收集器会有这种行为。

整堆收集(Full GC) : 收集整个Java堆和方法区的垃圾收集

13.2 标记-清除算法

算法分为“标记”和“清除”两个阶段:首先标记出所有需要回 收的对象, 在标记完成后,统一回收掉所有被标记的对象, 也可以反过来, 标记存活的对象, 统一回收所有未被标记的对象。
缺点:
1.执行效率不稳定,一般新生代中大部分对象都是要被回收的,需要进行大量标记和清楚动作
2.内存空间的碎片化问题,标记、 清除之后会产生大量不连续的内存碎片, 空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
在这里插入图片描述

13.3 标记-复制算法(年轻代使用)

内存按容量划分为大小相等的两块, 每次只使用其中的一块。 当这一块的内存用完了, 就将还存活着的对象复制到另外一块上面, 然后再把已使用过的内存空间一次清理掉。
缺点:
1.可用的对象区域减小一半,总体的GC更加频繁了
2.如果出现存活对象数量比较多的时候,需要复制较多的对象,成本上升,效率降低
3.如果99%的对象都是存活的(老年代),那么老年代是无法使用这种算法的。
在这里插入图片描述

13.4 标记-整理算法(老年代使用)

分为标记和整理两个阶段,其中的标记过程仍然与“标记-清除”算法一样, 但后续步骤不是直接对可回收对象进行清理, 而是让所有存活的对象都向内存空间一端移动, 然后直接清理掉边界以外的内存。
缺点:
1.是否移动对象都存在弊端, 移动则内存回收时会更复杂, 不移动则内存分配时会更复杂。
在这里插入图片描述

14.垃圾收集器

14.1 垃圾收集器分类

在这里插入图片描述
串行垃圾回收(Serial):单线程,适合client端使用,可以减少线程切换的开销。
并行垃圾回收(Parallel):多个垃圾收集器线程并行工作,同样会暂停用户线程,适用于科学计算、大数据后台处理等多交互场景。
并发垃圾回收(CMS):用户线程和垃圾回收线程同时执行,不一定是并行的,可能是交替执行,可能一边垃圾回收,一边运行应用线程,不需要停顿用户线程,互联网应用程序中经常使用,适用对响应时间有要求的场景。
G1垃圾回收:G1垃圾回收器将堆内存分割成不同的区域然后并发地对其进行垃圾回收。

14.2 主流垃圾回收器

新生代垃圾收集器:Serial 、 ParNew 、Parallel Scavenge
老年代垃圾收集器:Serial Old 、 Parallel Old 、CMS
整理收集器:G1

14.3 回收器组合关系

图中,实线表示正常组合关系,虚线表示弃用的组合关系。
在这里插入图片描述
JDK8中默认使用组合是: Parallel Scavenge GC 、ParallelOld GC
JDK9默认是:G1为垃圾收集器
JDK14 弃用了: Parallel Scavenge GC 、Parallel OldGC
JDK14 移除了:CMS GC

14.4 GC性能指标

吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间 ))。例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%
暂停时间:执行垃圾回收时,程序的工作线程被暂停的时间
内存占用:java堆所占内存的大小
收集频率:垃圾收集的频次

14.5 各收集器介绍

Serial和Serial Old
Serial回收新生代,Serial Old回收老年代,均采用单线程,适合client端,单CPU情况。
使用方式:-XX:+UseSerialGC
ParNew
多线程,回收新生代,单CPU时效果比Serial差,多CPU时比Serial好。
使用方式:-XX:+UseParNewGC,设置线程数: XX:ParllGCThreads
Parallel Scavenge
和ParNew差不多,区别是Parallel Scavenge以吞吐量为主,有自适应调节策略,自动指定年轻代、Eden、Suvisor区的比例。
使用方式:-XX:+UseParallelGC,
-设置最大垃圾收集停顿时间:-XX:MaxGCPauseMillis(毫秒),
-设置吞吐量:-XX:GCTimeRatio(当值为n时(n大于0小于100),GC所用时间占比不超过1/(1+n),n越大GC占比越小),
-设置年轻代线程数:XX:ParllGCThreads(当cpu合数小于等于8,默认cpu核数相同; 当cpu核数超过8, ParllGCThreads设置为 3+(5*CPU_COUNT)/8),
-设置自适应调节:-XX:+UseAdaptiveSizePolicy
Parallel Old
多线程, 回收老年代
使用方式:-XX:+UseParallelOldGC
CMS 收集器
收集分4个步骤:
1) 初始标记(需要stopTheWorld):标记处GC Roots 能够关联到的对象
2) 并发标记:遍历整个对象图的过程
3) 重新标记(需要stopTheWorld):修正并发标记期间因为用户继续运行而导致标记产生变动的那一部分对象的标记记录
4) 并发清除:删除掉标记判断已经死亡的对象,并释放内存空间
缺点:
1.对CPU资源敏感,如果本身应用CPU负载就高,还要并发进行垃圾回收,会导致应用执行效率降低
2.无法处理浮动垃圾,可能出现"Concurrent Mode Failure"失败而导致另一次Full GC的产生
3.空间碎片:CMS是一款基于标记-清除算法实现的收集器,所以会有空间碎片的现象。(在满足一定条件下会使用一次标记-整理算法)

浮动垃圾:并发标记过程中,某个对象先被标记是有引用的,之后可能引用断开了,但标记未清除,因此不会被回收,要等到下一次GC才会被回收。

G1收集器

  1. G1把内存划分为多个独立的区域Region
  2. G1仍然保留分代思想,保留了新生代和老年代,但他们不再是物理隔离,而是一部分Region的集合
  3. G1能够充分利用多CPU、多核环境硬件优势,尽量缩短STW
  4. G1整体整体采用标记整理算法,局部是采用复制算法,不会产生内存碎片
  5. G1的停顿可预测,能够明确指定在一个时间段内,消耗在垃圾收集上的时间不超过设置时间
  6. G1跟踪各个Region里面垃圾的价值大小,会维护一个优先列表,每次根据允许的时间来回收价值最大的区域,从而保证在有限事件内高效的收集垃圾
    在这里插入图片描述

其中Humongous内存区域存储大对象,一般被视为老年代。

收集分4个步骤:

  1. 初始标记 :和CMS一样只标记GC Roots直接关联的对象
  2. 并发标记 :进行GC Roots Traceing过程
  3. 最终标记 :修正并发标记期间,因程序运行导致发生变化的那一部分对象
  4. 筛选回收 :根据时间来进行价值最大化收集
    在这里插入图片描述

14.6 JVM命令的-、-X和-XX参数含义

先说VM选项, 三种:

  • : 标准VM选项,VM规范的选项
    -X: 非标准VM选项,不保证所有VM支持
    -XX: 高级选项,高级特性,但属于不稳定的选项参见 Java HotSpot VM Options
    例如:-Xss: 线程栈大小,等同于-XX:ThreadStackSize
    助记: -Xmm(memory maximum),-Xms(memory startup), -Xmn(memory nursery/new), -Xss(stack size).

15.常用指令

15.1 jps

查看java进程,经常用来获取java进程的pid(进程号)

15.2 jinfo

查看JVM参数和动态修改部分JVM参数
jinfo -flags ${pid}:查看进程的jvm参数
jinfo -flag [+|-] 动态打开或关闭参数
jinfo -flag = 动态设置参数

15.3 jstat

使用频率比较高的命令,主要用来查看JVM运行时的状态信息,包括内存状态、垃圾回收等
jstat -gc ${pid}:查看gc统计信息
jstat -gcutil ${pid}:查看gc统计信息(百分比)

15.4 jstack

查看JVM线程快照的命令
jstack ${pid} 查看jvm线程快照信息
jstack -l ${pid} 查看jvm线程快照信息(显示关于锁的附加信息,可以查看有无死锁)

cpu占用过高问题处理办法:
1.使用Process Explorer工具找到cpu占用率较高的线程
2.在thread卡中找到cpu占用高的线程id
3.线程id转换成16进制
4.使用jstack -l 查看进程的线程快照
5.线程快照中找到指定线程,并分析代码

15.5 jmap

生成 java程序的 dump 文件, 也可以查看堆内对象示例的统计信息、查看 ClassLoader 的信息以及finalizer队列
jmap -heap ${pid} :打印java heap(堆)信息,包括堆年轻代、老年代的配置、实际使用情况
jmap -histo[:live] ${pid} :打印堆中的java对象统计信息(一般内容会很多,不要直接打印在控制台,而是配合文件输出)
jmap -clstats ${pid} :打印类加载器信息
jmap -finalizerinfo ${pid}:打印在f-queue中等待执行finalizer方法的对象
jmap -dump ${pid}: 生成java堆的dump文件

dump-options:
live 只转储存活的对象,如果没有指定则转储所有对象
format=b 二进制格式
file= 转储文件到

常用示例:jmap -dump:live,format=b,file=D://dump.bin ${pid}
该文件需要专门的程序解析才能阅读(见jhat)

15.6 jhat

用来分析jmap生成dump文件的命令,jhat内置了应用服务器,可以通过网页查看dump文件分析结果,jhat一般是用在离线分析上。
jhat [option] [dumpfile]

option参数解释:
-stack false: 关闭对象分配调用堆栈的跟踪
-refs false: 关闭对象引用的跟踪
-port : HTTP服务器端口,默认是7000
-debug : debug级别
-version 分析报告版本

16.JVM常用工具

16.1 Jconsole

JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监测工具
启动方式:
1.直接用jconsole命令
2.在%JAVA_HOME%\bin目录下找到jconsole.exe启动

16.2 VisualVM

VisualVM 是一个工具,它提供了一个可视界面,用于查看 Java 虚拟机 (Java Virtual Machine, JVM) 上运行的基于Java 技术的应用程序(Java 应用程序)的详细信息。JDK1.6后产生的,可以说是Jconsole 的升级版。
启动方式:同jconsole
插件安装:点击"工具"→"插件"→"可用插件"选项, 选择所需的插件安装(例如Visual GC )。

16.3 GCeasy

这是一个在线分析日志的工具,主要功能是免费的,存在部分收费,地址:https://gceasy.io/
使用方法:
1.GC参数加上打印日志信息例如:
-XX:+PrintGCDetails(打印GC详细信息)、
-XX:+PrintGCDateStamps(输出GC的时间戳(以日期的形式))、
-Xloggc:…/logs/gc.log(指定输出路径收集日志到日志文件)
2.运行程序获取gc日志,将 gc.log日志上传gceasy网站
3.查看分析报告

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值