类加载流程和内存分配
堆内存= 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
串行回收器的特点:
- 只使用单线程进行GC;
- 独占式的GC。
串行收集器是JVM Client 模式下默认的垃圾收集器。
所有应用线程停止,直到JVM GC线程执行完成后,才继续执行。(GC过程中没有任何新的对象的生成)
JVM参数 | 作用 |
---|---|
-XX:SurvivorRatio | 设置eden区与survivor区比例 |
-XX:PretenureSizeThreshold | 设置最大对象直接进入老年代的阈值 |
-XX:MaxTenuringThreshold | 设置对象进入老年代的年龄阈值 |
启用指定收集器
JVM参数 | 新生代 | 老年代 |
---|---|---|
-XX:+UseSerialGC | 串行回收器 | 串行回收器 |
-XX:+UseParNewGC | ParNew | 串行回收器 |
-XX:+UseParallelGC | ParallelGC | 串行回收器 |
2.2 并行回收器
ParNew & ParallelGC & ParallelOldGC
将串行回收器多线程化。 与串行回收器有相同的回收策略、 算法、参数。
与串行回收器唯一的区别就是多线程化。
启用指定收集器
JVM参数 | 新生代 | 老年代 |
---|---|---|
-XX:+UseParNewGC | ParNew | 串行回收器 |
-XX:+UseConcMarkSweepGC | ParallelGC | CMS |
-XX:+UseParallelGC | ParallelGC | 串行回收器 |
-XX:+UseParallelOldGC | ParallelGC | ParallelOldGC |
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:CMSCompactAtFullCollection | GC后,进行一次碎片整理 |
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:+PrintGCApplicationStoppedTime | GC产生停顿的时间 |
-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 | 获得性能统计相关数据 |