JVM原理与实战

2 篇文章 0 订阅

类加载流程和内存分配

在这里插入图片描述
堆内存= Eden内存 + S0/S1内存 + 老年代内存

新生代中有90%的使用中,其余的10%是空闲的。

栈帧操作

在这里插入图片描述
针对Java中的方法引入了栈帧的概念。

方法执行的过程对应入栈和出栈的过程。

栈帧遵循先入后出的规则。

栈帧包括:局部变量表、操作数栈、帧数据区。

一、JVM垃圾回收算法

主动加载的几种方式?

  • Student stu = new Student();
  • 反射的方式
  • 克隆clone
  • 子类初始化的时候,父类会被优先初始化。
  • 调用静态的方法 static

符号引用和直接引用

使用命令查看 ( javap -V 类名 )

import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.io.Serializable;

@TableName("my_user")
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class UserEntity implements Serializable {

    @TableId(type = IdType.AUTO, value = "id")
    private int id; // 数据库Id

    @TableField(value = "user_name")
    private String userName; // 账号

    @TableField(value = "pass_word")
    private String passWord; // 密码

}

使用命令 : javap -v UserEntity.class

    #1 = Fieldref           #4.#79        // com/miyuan/wm/pojo/UserEntity.id:I
    #2 = Fieldref           #4.#80        // com/miyuan/wm/pojo/UserEntity.userName:Ljava/lang/String;
    #3 = Fieldref           #4.#81        // com/miyuan/wm/pojo/UserEntity.passWord:Ljava/lang/String;
    #4 = Class              #82           // com/miyuan/wm/pojo/UserEntity
    #5 = Methodref          #4.#83        // com/miyuan/wm/pojo/UserEntity.canEqual:(Ljava/lang/Object;)Z
    #6 = Methodref          #4.#84        // com/miyuan/wm/pojo/UserEntity.getId:()I
    #7 = Methodref          #4.#85        // com/miyuan/wm/pojo/UserEntity.getUserName:()Ljava/lang/String;
    #8 = Methodref          #21.#86       // java/lang/Object.equals:(Ljava/lang/Object;)Z
    #9 = Methodref          #4.#87        // com/miyuan/wm/pojo/UserEntity.getPassWord:()Ljava/lang/String;
   #10 = Methodref          #21.#88       // java/lang/Object.hashCode:()I
   #11 = Class              #89           // java/lang/StringBuilder
   #12 = Methodref          #11.#90       // java/lang/StringBuilder."<init>":()V
   #13 = String             #91           // UserEntity(id=
   #14 = Methodref          #11.#92       // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #15 = Methodref          #11.#93       // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   #16 = String             #94           // , userName=
   #17 = String             #95           // , passWord=
   #18 = String             #96           // )
   #19 = Methodref          #11.#97       // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #20 = Methodref          #21.#90       // java/lang/Object."<init>":()V
   #21 = Class              #98           // java/lang/Object
   #22 = Class              #99           // java/io/Serializable
   #23 = Utf8               id
   #24 = Utf8               I
   #25 = Utf8               RuntimeVisibleAnnotations
   #26 = Utf8               Lcom/baomidou/mybatisplus/annotations/TableId;
   #27 = Utf8               type
   #28 = Utf8               Lcom/baomidou/mybatisplus/enums/IdType;
   #29 = Utf8               AUTO
   #30 = Utf8               value
   #31 = Utf8               userName
   #32 = Utf8               Ljava/lang/String;
   #33 = Utf8               Lcom/baomidou/mybatisplus/annotations/TableField;
   #34 = Utf8               user_name
   #35 = Utf8               passWord
   #36 = Utf8               pass_word
   #37 = Utf8               getId
   #38 = Utf8               ()I
   #39 = Utf8               Code
   #40 = Utf8               LineNumberTable
   #41 = Utf8               LocalVariableTable
   #42 = Utf8               this
   #43 = Utf8               Lcom/miyuan/wm/pojo/UserEntity;
   #44 = Utf8               getUserName
   #45 = Utf8               ()Ljava/lang/String;
   #46 = Utf8               getPassWord
   #47 = Utf8               setId
   #48 = Utf8               (I)V
   #49 = Utf8               MethodParameters
   #50 = Utf8               setUserName
   #51 = Utf8               (Ljava/lang/String;)V
   #52 = Utf8               setPassWord
   #53 = Utf8               equals
   #54 = Utf8               (Ljava/lang/Object;)Z
   #55 = Utf8               o
   #56 = Utf8               Ljava/lang/Object;
   #57 = Utf8               other
   #58 = Utf8               this$userName
   #59 = Utf8               other$userName
   #60 = Utf8               this$passWord
   #61 = Utf8               other$passWord
   #62 = Utf8               StackMapTable
   #63 = Class              #82           // com/miyuan/wm/pojo/UserEntity
   #64 = Class              #98           // java/lang/Object
   #65 = Utf8               canEqual
   #66 = Utf8               hashCode
   #67 = Utf8               PRIME
   #68 = Utf8               result
   #69 = Utf8               $userName
   #70 = Utf8               $passWord
   #71 = Utf8               toString
   #72 = Utf8               <init>
   #73 = Utf8               (ILjava/lang/String;Ljava/lang/String;)V
   #74 = Utf8               ()V
   #75 = Utf8               SourceFile
   #76 = Utf8               UserEntity.java
   #77 = Utf8               Lcom/baomidou/mybatisplus/annotations/TableName;
   #78 = Utf8               my_user
   #79 = NameAndType        #23:#24       // id:I
   #80 = NameAndType        #31:#32       // userName:Ljava/lang/String;
   #81 = NameAndType        #35:#32       // passWord:Ljava/lang/String;
   #82 = Utf8               com/miyuan/wm/pojo/UserEntity
   #83 = NameAndType        #65:#54       // canEqual:(Ljava/lang/Object;)Z
   #84 = NameAndType        #37:#38       // getId:()I
   #85 = NameAndType        #44:#45       // getUserName:()Ljava/lang/String;
   #86 = NameAndType        #53:#54       // equals:(Ljava/lang/Object;)Z
   #87 = NameAndType        #46:#45       // getPassWord:()Ljava/lang/String;
   #88 = NameAndType        #66:#38       // hashCode:()I
   #89 = Utf8               java/lang/StringBuilder
   #90 = NameAndType        #72:#74       // "<init>":()V
   #91 = Utf8               UserEntity(id=
   #92 = NameAndType        #100:#101     // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #93 = NameAndType        #100:#102     // append:(I)Ljava/lang/StringBuilder;
   #94 = Utf8               , userName=
   #95 = Utf8               , passWord=
   #96 = Utf8               )
   #97 = NameAndType        #71:#45       // toString:()Ljava/lang/String;
   #98 = Utf8               java/lang/Object
   #99 = Utf8               java/io/Serializable
  #100 = Utf8               append
  #101 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #102 = Utf8               (I)Ljava/lang/StringBuilder;

1.1 什么是垃圾(Garbage Collection)回收?

垃圾是指存在于内存中的、不会再被使用(失去引用)的对象

回收是指清除内存中的“垃圾”对象。

1.2 引用计数法(Reference Conting)

对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1, 当引用失效时,引用计数器就减1.只要对象A的引用计数器的值为0,则对 象A就不可能再被使用。

缺点:

  • 无法处理循环引用的情况;(这时候A和B都是1,实际它俩都已经是垃圾了)
    在这里插入图片描述

  • 引用计数器要求在每次因引用产生和消除的时候,需要伴随一个加或减法操作,对系统性能会有一定的影响。

所以,JVM没有采用引用计数法作为垃圾回收算法。

1.3 标记清除法 *(Mark - Sweep)

标记清除算法是现代垃圾回收算法的思想基础。

分为两个阶段:标记阶段清除阶段

图中:黑色为垃圾,白色为空闲内存区域。

在这里插入图片描述
缺点:

  • 就是清除之后的空间碎片
  • 不连续的内存空间分配效率低于连续空间。

1.4 复制算法 *(Copying)

将原有内存空间分为两块。每次只使用其中一块内存,例如:A内存, GC时将存活的对象复制到B内存中。然后清除掉A内存所有对象。开始使用B内存。循环交替使用A内存和B内存。
加粗样式
复制算法没有内存碎片,并且如果垃圾对象很多,那么这种算法效率很高。
但是它的缺点是系统内存只能使用1/2。适用于新生代,因为新生代大多都是生命周期比较短的对象,垃圾对象较多。

该算法解决了标记清除算法效率低的问题,并且没有内存碎片,但是只能使用一半的系统内存,适用于新生代。

1.4.1 复制算法在JVM中的应用

新生代和老年代对应的是对象,新对象和老对象。

因为新生代大多对象都是生命周期比较短的“朝不保夕”,所以在新生代串行GC中,使用了复制算法。

比例:

  • Eden区:S0:S1 = 8:1:1 , 真正使用的是 9:1
  • 新生代:老年代 = 1:2

在这里插入图片描述
说明:首先Eden区和Survivor From区用来存放对象,内存区域满了后,经过复制算法,将Eden区和Survivor From区的存活对象复制到Survivor To区,Eden区和Survivor From区的对象全部清除。此时,Survivor From区和Survivor To区的身份互换,由Eden区和Survivor To区来存放对象。

这样利用的复制算法的优点,也空间的浪费降到极低。

对象在什么情况下进入老年代?

  • 大的对象;(比如设置10KB就是大对象)
  • 老的对象;(比如设置8次GC后还存活的对象进入老年代)
  • Survivor To区空间不足。

finalize()方法只会被调用一次,给对象唯一一次重生的机会。

1.5 标记压缩法 *(Mark - Compact)

标记压缩算法是一种老年代的回收算法。

它首先标记存活的对象,然后将所有存 活的对象压缩到内存的一端,然后在清理所有存活对象之外的空间。

该算法不会产生内存碎片,并且也不用将内存一分为二。所以其性价比比较高。
在这里插入图片描述

1.6 分代算法

将堆空间划分为新生代和老年代,根据它们直接的不同特点,执行不同的回收算法,提升回收效率。
在这里插入图片描述

复制算法回收效率比较高,所以适用于新生代。

1.7 分区算法

将堆空间划分成连续的不同小区间,每个区间独立使用、回收。由于当堆空间大时,一 次GC的时间会非常耗时,那么可以控制每次回收多少个小区间,而不是整个堆空间, 从而减少一次GC所产生的停顿。

在这里插入图片描述
根据内存空间的占比来确定回收哪几块小区间。(空间换时间)

二、JVM垃圾收集器

2.1 串行回收器

Serial

串行回收器的特点:

  1. 只使用单线程进行GC;
  2. 独占式的GC。

串行收集器是JVM Client 模式下默认的垃圾收集器。

所有应用线程停止,直到JVM GC线程执行完成后,才继续执行。(GC过程中没有任何新的对象的生成)
在这里插入图片描述
在这里插入图片描述

JVM参数作用
-XX:SurvivorRatio设置eden区与survivor区比例
-XX:PretenureSizeThreshold设置最大对象直接进入老年代的阈值
-XX:MaxTenuringThreshold设置对象进入老年代的年龄阈值

启用指定收集器

JVM参数新生代老年代
-XX:+UseSerialGC串行回收器串行回收器
-XX:+UseParNewGCParNew串行回收器
-XX:+UseParallelGCParallelGC串行回收器

2.2 并行回收器

ParNew & ParallelGC & ParallelOldGC

将串行回收器多线程化。 与串行回收器有相同的回收策略、 算法、参数。

与串行回收器唯一的区别就是多线程化。
在这里插入图片描述
在这里插入图片描述

启用指定收集器

JVM参数新生代老年代
-XX:+UseParNewGCParNew串行回收器
-XX:+UseConcMarkSweepGCParallelGCCMS
-XX:+UseParallelGCParallelGC串行回收器
-XX:+UseParallelOldGCParallelGCParallelOldGC
CMS

老年代的回收器。

在这里插入图片描述

  • 初始标记:标记所有的根节点;
  • 并发标记:标记所有对象;
  • 预清理:可以不执行,预判下一次GC的时间和这次会不会重叠,尽量避免新生代与重新标记的对象的重复,减少一次停顿时间。
  • 重新标记:第二步和第三步可能存在新生成的对象,所以这里进行标记对象的一次修正。
  • 并发清理:
  • 并发重置:
JVM参数
收集器JVM参数作用
ParNew-XX:ParallelGCThreads指定GC时工作的线程数量
ParallelGC-XX:MaxGCPauseMilis最大的垃圾收集暂停时间
ParallelGC-XX:GCTimeRatio设置垃圾收集吞吐量
ParallelGC-XX:+UseAdaptiveSizePolicy打开自适应垃圾收集策略
ParallelOldGC-XX:ParallelGCThreads指定GC时工作的线程数量
CMS-XX:-CMSPrecleaningEnabled禁用预清理操作
CMS-XX:ConcGCThreads设置并发线程数量
CMS-XX:ParallelCMSThreads设置并发线程数量
CMS-XX:CMSInitiatingOccupancyFration当老年代空间使用量达到某百分比时,会执行CMS。默认68
CMS-XX:CMSCompactAtFullCollectionGC后,进行一次碎片整理
CMS-XX:CMSFullGCsBeforeCompaction指定执行多少次GC后,进行一次碎片整理

2.3 G1的GC收集过程

因为CMS回收效率不高,所以产生了一个G1收集器来替代CMS。
G1全称Garbage First Garbage Collector。优先回收垃圾比例最高的区域。 G1收集器将堆划分为多个区域,每次收集部分区域来减少GC产生的停顿时间。

针对新生代+老年的的全部收集。既是分区算法,也是分代算法。
在这里插入图片描述

新生代GC

在这里插入图片描述

并发标记周期

在这里插入图片描述

  • 初始标记:标记根节点;(进行一次新生代GC)
  • 根区域扫描:因为新生代GC,eden区被清空,survivor去里面有一些对象会直接进入到老年代,所以进行根区域的再次扫描;
  • 并发标记:标记堆里面的存货对象;
  • 重新标记:可能有新对象的生成,进行最后一次标记,最后一次修正;
  • 独占清理:计算存货对象,计算GC的比例;
  • 并发清理:清理完全空闲的区域。
混合收集

循环执行三阶段。
在这里插入图片描述
在这里插入图片描述

JVM参数作用
-XX:UseG1GC打开G1收集器开关
-XX:MaxGCPauseMilis指定目标最大停顿时间
-XX:ParallelGCThreads设置并发线程数量
-XX:+InitatingHeapOccupancyPercent指定堆得使用率是多少时,触发并发标记周期,默认是45

三、JVM常用参数

执行语法:Java [-options] [package+className] [arg1,arg2,…,argN]

JVM参数作用
-Xms128m设置初始化堆内存为128M
-Xmx512m设置最大堆内存为512M
-Xmn160m设置新生代大小为-Xmn160M(
-Xss128m设置最大栈内存为128M
-XX:SurvivorRatio设置新生代eden区与from/to空间的比例关系
-XX:PermSize=64M设置初始永久区64M
-XX:MaxPermSize=128M设置最大永久区128M
-XX:MaxMetaspaceSize设置元数据区大小(JDK1.8 取代永久区)
-XX:+DoEscapeAnalysis启用逃逸分析(Server模式)
-XX:+EliminateAllocations开启标量替换(默认开启)
-XX:+TraceClassLoading跟踪类的加载
-XX:+TraceClassUnloading跟踪类的卸载
-Xloggc:gc.log将gc日志信息打印到gc.log文件中
JVM参数作用
-XX:+PrintGC打印GC日志
-XX:+PrintGCDetails打印GC详细日志
-XX:+PrintGCTimeStamps输出GC发生的时间
-XX:+PrintGCApplicationStoppedTimeGC产生停顿的时间
-XX:+PrintGCApplicationConcurrentTime应用执行的时间
-XX:+PrintHeapAtGC在GC发生前后,打印堆栈日志
-XX:+PrintReferenceGC打印对象引用信息
-XX:+PrintVMOptions打印虚拟机参数
-XX:+PrintCommandLineFlags打印虚拟机显式和隐式参数
-XX:+PrintFlagsFinal打印所有系统参数
-XX:+PrintTLAB打印TLAB相关分配信息
-XX:+UseTLAB打开TLAB
-XX:TLABSize设置TLAB大小
-XX:+ResizeTLAB自动调整TLAB大小
-XX:+DisableExplicitGC禁用显示GC (System.gc())
-XX:+ExplicitGCInvokesConcurrent使用并发方式处理显式GC

四、JVM监控优化

性能监控工具

在这里插入图片描述

Linux

top命令(常用)

能够实时显示系统中各个进程的资源占用情况。 分为两部分:系统统计信息&进程信息。

  • 系统统计信息:

    Line1:任务队列信息,从左到右依次表示:系统 当前时间、系统运行时间、当前登录用户数。 Load average表示系统的平均负载,即任务队 列的平均长度——1分钟、5分钟、15分钟到现 在的平均值。

    Line2:进程统计信息,分别是:正在运行进程数、 睡眠进程数、停止的进程数、僵尸进程数。

    Line3:CPU统计信息。us表示用户空间CPU占用率、sy表示内核空间CPU占用率、ni表示用户进 程空间改变过优先级的进程CPU占用率。id表示空闲CPU占用率、wa表示待输入输出的CPU时间 百分比、hi表示硬件中断请求、si表示软件中断请求。

    Line4:内存统计信息。从左到右依次表示:物理内存总量、已使用的物理内存、空闲物理内存、 内核缓冲使用量。

    Line5:从左到右表示:交换区总量、已使用交换区大小、空闲交换区大小、缓冲交换区大小。
    在这里插入图片描述

  • 进程信息:

    PID:进程id
    USER:进程所有者
    PR:优先级
    NI:nice值,负值->高优先级,正值->低优先级
    VIRT:进程使用虚拟内存总量 VIRT=SWAP+RES
    RES:进程使用并未被换出的内存。CODE+DATA
    SHR:共享内存大小
    S:进程状态。 D=不可中断的睡眠状态 R=运行 S=睡眠 T=跟踪/停止 Z=僵尸进程
    %CPU:上次更新到现在的CPU时间占用百分比
    %MEM:进程使用的物理内存百分比 TIME+:进程使用的CPU时间总计,单位 1/100秒

    COMMAND:命令行

vmstat命令

性能监测工具,显示单位均为kb。它可以统计CPU、内存使用情况、swap使用情况等信息,也可 以指定采样周期和采用次数。例如:每秒采样一次,共计3次。
在这里插入图片描述

procs列:r表示等待运行的进程数。b表示处于非中断睡眠状态的进程数。
memory列:swpd表示虚拟内存使用情况。free表示空闲内存量。buff表示被用来作为缓存的内存。
swap列:si表示从磁盘交换到内存的交换页数量。so表示从内存交换到磁盘的交换页数量。 io列:bi表示发送到块设备的块数,单位:块/秒。bo表示从块设备接收到的块数。
system列:in表示每秒的中断数,包括时钟中断。cs表示每秒的上下文切换次数。
cpu列:us表示用户cpu使用时间。sy表示内核cpu系统使用时间。id表示空闲时间。 wa表示等待io时间。

iostat命令

可以提供详尽的I/O信息。
如果只看磁盘信息,可以使用-d参数。即:Iostat –d 1 3 (每1秒采集一次持续3次)
在这里插入图片描述

命令说明
tps 列表示该设备每秒的传输次数。
Blk_read/s列表示每秒读取块数。
Blk_wrtn/s列表示每秒写入块数。
Blk_read列表示读取块数总量。
Blk_wrtn列表示写入块数总量。

JDK

jps(常用)

ps -ef | grep 648

用于列出Java的进程。 jps [-options]

命令说明
jps列出java进程id和类名
jps –q仅列出java进程id
jps –m输出java进程的入参
jps –l输出主函数的完整路径
jps –v显示传递给JVM的参数
jstat

用于查看堆中的运行信息。 jstat –help jstat -options jstat <-option> [-t] [-h] [ []]

如:jstat -class -t 648 1000 5 查看进程648的ClassLoader相关信息,每1000毫秒打印1次, 一共打印5次,并输出程序启动到此刻的Timestamp数。
在这里插入图片描述

命令说明
jstat -compiler -t 648查看指定进程的编译信息。
jstat -gc 648查看指定进程的堆信息。
jstat -gccapacity 648查看指定进程中每个代的容量与使用情况
jstat -gccause 648显示最近一次gc信息
jstat -gcmetacapacity 648查看指定进程的元空间使用信息
jstat -gcnew 648查看指定进程的新生代使用信息
jstat -gcnewcapacity 648查看指定进程的新生代各区大小信息
jstat -gcold 648查看指定进程的老年代使用信息
jstat -gcoldcapacity 648查看指定进程的老年代各区大小信息
jstat -gcutil 648查看指定进程的GC回收信息
jstat -printcompilation 648查看指定进程的JIT编译方法统计信息
jinfo

用于查看运行中java进程的虚拟机参数。jinfo [option]

命令说明
jinfo -flag MaxTenuringThreshold 648查看进程648 的虚拟机参数MaxTenuringThreshold 的值
jinfo -flag +PrintGCDetails 648动态添加进程648 的虚拟机参数+PrintGCDetails,开 启GC日志打印。
jinfo -flag -PrintGCDetails 648动态添加进程648 的虚拟机参数+PrintGCDetails,关 闭GC日志打印
jmap(常用)

生成指定java进程的dump文件;可以查看堆内对象实例的统计信息,查看ClassLoader 信息和finalizer队列信息。 jmap [option]

命令说明
jmap -histo 648 > /Users/muse/a.txt输出进程648 的实例个数与合计到文件a.txt中
jmap -dump:format=b,file=/Users/muse/b.hprof 648输出进程648 的堆快照,可使用jhat、 visual VM等进行分析
jhat

命令用于分析jmap生成的堆快照jhat [-stack ] [-refs ] [-port ] [-baseline ] [-debug ] [- version] [-h|-help]

命令说明
jhat b.hprof分析jmap生成的堆快照b.hprof, http://127.0.0.1:7000通过这个地址查看。 OQL(Object Query Language)
jstack(常用)

命令用于导出指定java进程的堆栈信息jstack [-l]

命令说明
jstack -l 648 > /Users/muse/d.txt输出进程648 的实例个数与合计到文件a.txt中 cat /Users/muse/d.txt
jcmd

命令用于导出指定java进程的堆栈信息,查看进程,GC等。 jcmd <pid | main class> <command …|PerfCounter.print|-f file>

命令说明
jcmd -l列出java进程列表
jcmd 648 help输出进程java进程为26586所支持的jcmd指令
jcmd 648 VM.uptime查看java进程启动时间
jcmd 648 Thread.print打印线程栈信息
jcmd 648 GC.class_histogram查看系统中类的统计信息
jcmd 648 GC.heap_dump /Users/muse/a.txt导出堆信息
jcmd 648 VM.system_properties获得系统的Properties内容
jcmd 648 VM.flags获得启动参数
jcmd 648 PerfCounter.print获得性能统计相关数据
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

抽抽了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值