JVM基础
java发展的重大事件
- 1995年5月23日,java语言诞生
- 1998年2月,JDK1.1被下载2,000,000万次
- 1996年,SUN公司发布java的三个版本,标准版(J2SE)、企业版(J2EE)、微型版(J2ME)即安卓
计算机体系结构:冯诺依曼计算机体系模型
高级语言
C、C++、Java、Python等。都要转换成机器能够懂的机器语言。
编程语言分为编译型和解释型语言。
编译型语言:执行速度快、效率高,依靠编译器,跨平台性差。
解释型语言:使用专门的解释器对源程序逐行解释成特定平台的机器码并立即执行。
java属于编译型+解释型的高级语言。
class文件通过JVM翻译才能在对应的平台上运行,而这个翻译大多数时候是解释的过程,但是也会有编译,称之为运行时编译,即JIT(Just In Time)。
JVM即java vittual machine(java虚拟机)
JVM加载顺序
-
loading(装载)
(1)先找个classfile–字节流–类加载classloader
(2)字节流–类静态数据结构–方法区
(3)字节流–class对象–堆
-
linking(链接)
(1)验证 文件格式、元数据、字节码、符号引用关系是否相等
是否以16进制cafebaby开头,版本号是否正确。
是否有父类。是否继承了final类,final类是不能被继承的。
-XVerify:none 取消验证
(2)准备 为类的静态变量进行分配内存,并且初始化当前类变量的默认值
(3)解析(Resolve) 类中的符号引用转变为直接应用。如果有了直接引用,那引用的目标必定存在内存中。
-
initiaing(初始化)
执行类构造器方法的过程。
初始化什么时候触发执行?
-
主动引用
- 创建类实例
- 访问类或接口的静态变量,或者对静态变量赋值
- 调用类的静态方法
- 反射(Class.forName(“com.thtf.Test”))
- 初始化类的子类,则其父类也会被初始化
- java虚拟机启动时被标明为启动类的类
-
被动引用
-
引用父类的静态字段,只会引起父类的初始化,不会引起子类的初始化。
-
定义类数组,不会引起类的初始化。
-
引用类的static final常量,不会引起类的初始化(如果只有static修饰,还是会引起该类初始化)
-
-
-
卸载
在类使用完之后,如果满足一下情况,类会被卸载
- 该类所有的实例都已经别回收,也就是java堆中不存在该类的任何实例
- 加载该类的ClassLoader已经被回收
- 该类对应的java.lang.Class对象没有任何地方被引用,无法在任务地方通过反射访问该类的方法。
类的加载机制
-
全盘负责
当一个类加载器负责加载某个class时,该class所依赖的和引用的其他class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入。
-
父类委托
双亲委派是指子类加载器如果没有加载过该目标类,就先委托父类加载器加载该目标类,只有在父类加载器找不到字节码文件的情况下才从自己的类路径中查找并装载目标类。
双亲委派机制只是java推荐的机制,并不是强制的机制。可以继承java.lang.ClassLoader类,实现自己的类加载器。如果想要保持双亲委派模型,就应该重写findClass(name)方法,如果要破坏双亲委派模型,可以重写loadClass(name)方法。
-
缓存机制
缓存机制会保证所有加载过的Class都将在内存中缓存,当程序中需要使用某个class时,类加载器先从内存的缓存中寻找该class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成class对象,存入缓存区。
Java内存区域
运行时数据区
-
方法区
跟java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
运行时常量池是方法区的一部分,并不是常量一定只有编译期才能产生,运行期间也可能将新的常量放入池中,比如String类的intern()方法。
-
虚拟机栈
线程私有的,生命周期同线程一样。java方法执行的内存模型。每个方法执行时都会创建一个栈帧用于存储局部变量、操作数栈、动态链接、方法出口等信息。如果线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常;如果虚拟机可以动态扩展,在扩展时无法申请到足够的内存,会抛出OutOfMemoryError。
动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。
-
本地方法栈
为虚拟机执行native方法服务。同样会抛出上面两个异常。
-
堆
对于大多数应用来说,Java堆是java虚拟机所管理的内存中最大的一块。是所有线程共享的一块区域,在虚拟机创建时创建。存放的是对象实例。是垃圾收集器管理的主要区域。当堆无法扩展时,会抛出OutOfMemoryError异常。
-
程序计数器
线程私有的,可以看做是当前线程所执行的字节码的行号指示器,此内存区域是唯一一个在java虚拟机规范中没有规定任何oom情况的区域。
如果线程正在执行java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是native方法,则这个计数器为空。
常量池:静态常量池,运行时常量池,字符串常量池。
静态常量池:存储的是字面量以及符号引用
运行时常量池:每个类、每个接口在jvm运行的过程中在内存中开辟出来的一块用来存储静态常量池部分数据的一块特殊区域
字符串常量池:包含在动态常量池里
大端存储:低序字节在前,高序字节在后。便于数据类型的符号判断,因为最低地址位数据即为符号位,可以直接判断数据的正负号。
小端存储:高序字节在前,低序字节在后。便于数据之间的类型转换,如long类型转换为int类型时,高位地址部分的数据可以直接截掉。
双亲委派机制
类的加载顺序:
bootstrap classloader---加载jre中lib/*.jar中的类
||
extension classloader---加载project中lib/*.jar中的类
||
app classloader---加载自定义的一些类
||
自定义加载器 通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据自身需要自定义的ClassLoader,如tomcat等
类加载的时候会先去bootstrap classloader中加载再去extension classloader中加载再去app classloader。
打破双亲委派
java想了几种办法可以用来打破双亲委派。
- SPI:java从1.6出了SPI是为了优雅的解决这类为题–jdk提供接口,供应商提供服务。如JDBC中的DriverManager。
- OSGI:实现模块化热部署的关键是他自定义的类加载器机制的实现。每个程序都有一个自己的类加载器。
JVM内存模型
jvisualvm可以打开本地虚拟机内存监视
垃圾回收(GC)
-
堆的GC及内存分配
对象的年龄是指垃圾回收的频率,回收一次age+1,堆中分为old和young,即新生代和老年代,GC的时候默认15还存在的放进老年代。内存空间的不连续性/空间碎片时young又分为Eden、Sorvivor区。Sorvivor分为s0和s1,eden用来分配新的对象,其中一个s用来保存存活的对象,另一个s用来空间保留(浪费)。新生代的s区不够放了,跟老年代借一些空间,叫担保机制(对象直接存储在老年代)
-
非堆的GC(方法区):JVM运行时数据区规范
落地:方法区:常量,静态变量,类信息,即时编辑器编译之后的代码。
JDK7 permspace 永久代 堆。
JDK8 metaspace 元空间 本地内存
永久代、元空间是对方法区的实现落地
(1)确定一个对象是否是垃圾有两种方法
-
引用计算法
计数器加1 : 弊端:当a和b对象互相引用时,就不能当做垃圾去回收即循环引用的问题
-
GC Roots可达性分析
GC Root条件:局部变量表变量,static成员
(2)什么时候会进行垃圾回收?
根据JVM自动完成的。Eden或者S区不足了执行 Minor GC,Old区不够用了执行 Major GC–通常会触发一次majorGC–即FUll GC
(3)怎么回收?
-
标记-清除
扫描堆内存的空间,比较耗时,会有空间碎片的问题
-
标记-复制
效率比较高,但是这时候会有空间的浪费
-
标记-整理
整理的过程非常复杂和耗时,不会有空间碎片,比较麻烦
-
分代回收
不同的代用不同的垃圾回收算法 。 young区:标记-复制算法 old区:标记清除,标记整理
(4)回收算法的落地–>>垃圾收集器
cms垃圾回收,并发执行 。
jdk7里面出现g1回收,jdk8推荐使用,jdk9默认的垃圾回收器
g1-garbage-firstl垃圾多的区域先回收,垃圾少的就先不处理
jdk11之后:gzc出现zero gc 停顿时间短,并且还有较高的吞吐量
回收器的选择
(1)停顿时间
(2)吞吐量
-
串行收集器->Serial和Serial old
只能有一个垃圾回收线程执行,用户线程暂停 适用于内存比较小的嵌入式设备
-
并行收集器(吞吐量优先)->Paralel Scanvenge、Parallel Old
多条垃圾回收线程并行工作,但此时用户线程仍然处于等待状态 适用于科学计算、后台处理等交互场景
-
并发收集器(停顿时间优先)->CMS、G1
用户线程和垃圾收集线程同时执行(但并不一定是并行的,可能是交替执行的),垃圾收集线程在执行的时候不会停顿用户线程的执行。
JVM参数系统
- 标准参数:不随着jdk版本的变化而变化
- -X参数[非标准 修改jvm的运行的方式
- -XX参数[非标准 a-boolean b-name = value
#java进程有多少参数
-XX:+PrintFlagsFinal
#查看当前有哪些命令
-XX:+PrintCommandLineFlags -version
#jdk里面常用的排查命令??
-Xms最小值,-Xmx最大值,一般设置一样,防止由于不断计算扩容问题带来的卡顿。
jps查看java进程
jinfo pid 查看java进程信息
jinfo -flag name PID 查看某个java进程的name属性的值
#jinfo -flag MaxHeapSize PID
#jinfo -flag UseG1GC PID
jinfo -flags PID 查看曾经赋过值的一些参数
jstat -class pid 500 10 查看类的装载信息,每500ms输出一次,输出10次
jstat -gc pid 500 10 查看垃圾收集的信息
jstack pid 查看线程堆栈信息(cpu彪高时可以查看)
jmap -histo pid查看当前进程中每个类占用的空间大小和实例数量(内存彪高)
jmap -heap pid查看堆的相关信息
jmap -dump:format=b,file=heap.hporf PID dump出堆内存相关信息
#在开发中,JVM参数可以加上下面两句,这样内存溢出时,会自动dump出该文件
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.hprof
thread -b查看哪个线程死锁
jad 在线反编译文件
#常见问题
#问:为什么程序启动的时候会发生full gc,查看老年代的空间还是比较充足?
#答:metaspace空间不够用导致的,适当增加metaspace空间
#常见的调优工具
jconsole,jvisualvm可以远程连接arthas在线排查工具
-X参数
非标准参数,也就是在jdk各个版本中可能会变动
-Xint 解释执行
-Xcomp 第一次使用就编译成本地代码
-Xmixed 混合模式,JVM自己决定
-XX参数
使用的最多的参数类型
非标准化参数,相对不稳定,主要用于JVM调优和Debug
#Boolean类型
格式:-XX:[+-]<name> +或-表示启用或者禁用name属性
例:-XX:+UseConcMarkSweepGC 表示启用CMS类型的垃圾回收器
-XX:+UseG1GC 表示启用G1类型的垃圾回收器
#非boolean类型
格式:-XX:<name>=<value> 表示name属性的值是value
例:-XX:MaxGCPauseMillis=500
其他参数
-Xms1000M 等价于 -XX:InitialHeapSize=1000M
-Xmx1000M 等价于 -XX:MaxHeapSize=1000M
-Xss100 等价于 -XX:ThreadStackSize=100
#相当于是-XX类型的参数
查看参数:java -XX:+PrintFlagsFinal -version > flags.txt
=表示默认值,:= 表示被用户或JVM修改后的值。1Byte(字节)=8bit(位)
- | 参数 | 含义 | 说明 |
| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| -XX:CICompilerCount=3 | 最大并行编译数 | 如果设置大于1,虽然编译速度会提高,但是同样影响系统稳定性,会增加JVM崩溃的可能 |
| -XX:InitialHeapSize=100M | 初始化堆大小 | 简写-Xms100M |
| -XX:MaxHeapSize=100M | 最大堆大小 | 简写-Xms100M |
| -XX:NewSize=20M | 设置年轻代的大小 | |
| -XX:MaxNewSize=50M | 年轻代最大大小 | |
| -XX:OldSize=50M | 设置老年代大小 | |
| -XX:MetaspaceSize=50M | 设置方法区大小 | |
| -XX:MaxMetaspaceSize=50M | 方法区最大大小 | |
| -XX:+UseParallelGC | 使用UseParallelGC | 新生代,吞吐量优先 |
| -XX:+UseParallelOldGC | 使用UseParallelOldGC | 老年代,吞吐量优先 |
| -XX:+UseConcMarkSweepGC | 使用CMS | 老年代,停顿时间优先 |
| -XX:+UseG1GC | 使用G1GC | 新生代,老年代,停顿时间优先 |
| -XX:NewRatio | 新老生代的比值 | 比如-XX:Ratio=4,则表示新生代:老年代=1:4,也就是新生代占整个堆内存的1/5 |
| -XX:SurvivorRatio | 两个S区和Eden区的比值 | 比如-XX:SurvivorRatio=8,也就是(S0+S1):Eden=2:8,也就是一个S占整个新生代的1/10 |
| -XX:+HeapDumpOnOutOfMemoryError | 启动堆内存溢出打印 | 当JVM堆内存发生溢出时,也就是OOM,自动生成dump文件 |
| -XX:HeapDumpPath=heap.hprof | 指定堆内存溢出打印目录 | 表示在当前目录生成一个heap.hprof文件 |
| -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:g1-gc.log | 打印出GC日志 | 可以使用不同的垃圾收集器,对比查看GC情况 |
| -Xss128k | 设置每个线程的堆栈大小 | 经验值是3000-5000最佳 |
| -XX:MaxTenuringThreshold=6 | 提升年老代的最大临界值 | 默认值为 15 |
| -XX:InitiatingHeapOccupancyPercent | 启动并发GC周期时堆内存使用占比 | G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示”一直执行GC循环”. 默认值为 45. |
| -XX:G1HeapWastePercent | 允许的浪费堆空间的占比 | 默认是10%,如果并发标记可回收的空间小于10%,则不会触发MixedGC。 |
| -XX:MaxGCPauseMillis=200ms | G1最大停顿时间 | 暂停时间不能太小,太小的话就会导致出现G1跟不上垃圾产生的速度。最终退化成FullGC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。 |
| -XX:ConcGCThreads=n | 并发垃圾收集器使用的线程数量 | 默认值随JVM运行的平台不同而不同 |
| -XX:G1MixedGCLiveThresholdPercent=65 | 混合垃圾回收周期中要包括的旧区域设置占用率阈值 | 默认占用率为 65% |
| -XX:G1MixedGCCountTarget=8 | 设置标记周期完成后,对存活数据上限为G1MixedGCLIveThresholdPercent的旧区域执行混合垃圾回收的目标次数 | 默认8次混合垃圾回收,混合回收的目标是要控制在此目标次数以内 |
| -XX:G1OldCSetRegionThresholdPercent=1 | 描述Mixed GC时,Old Region被加入到CSet中默认情况下,G1只把10%的Old Region加入到CSet中 | 默认情况下,G1只把10%的Old Region加入到CSet |
垃圾收集器
Serial
最基本,发展历史最悠久的收集器,jdk1.3之前是虚拟机新生代收集的唯一选择。
它是一种单线程收集器,它只会使用一个cpu或者一条收集线程完成垃圾收集工作,并且进行垃圾收集的时候需要暂停其他线程。
优点:简单高效,拥有很高的单线程收集效率
缺点:收集过程需要暂停所有线程
算法:复制算法
适用范围:新生代
应用:client模式下的默认新生代收集器
Serial Old
是Serial收集器的老年代版本,也是一个单线程收集器,不同的是采用“标记-整理算法”,运行过程和Serial收集器一样。
ParNew
可以把这个收集器理解为Serial收集器的多线程版本。
优点:在多Cpu时,比Serial效率高
缺点:收集过程需要暂停所有线程,单cpu时彼Serial效率差
算法:复制算法
适用范围:新生代
应用:运行在Server模式下的虚拟机中首选的新生代收集器
Parallel Scavenge
是一个新生代收集器,也是使用复制算法的收集器,又是并行的多线程收集器,看上去和ParNew一样,但是这个更关注系统的吞吐量。
-XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间,
-XX:GCTimeRatio直接设置吞吐量的大
Parallel Old
是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法进行垃圾回收,也是更加关注吞吐量。
CMS
一种以获取最短回收停顿时间为目标的收集器。采用的是标记-清除算法,真个过程分为4步
- 初始标记 CMS initial mark 标记GC Roots直接关联对象,不用Tracing,速度很快
- 并发标记 CMS concurrent mark 进行GC Root Tracing
- 重新标记 CMS remark 修改并发标记因用户程序变动的内容
- 并发清除 CMS concurrent sweep 清楚不可达对象回收空间,同时有新垃圾生成,留着下次清理成为浮动垃圾
整个过程中,并发标记和并发清除,收集线程可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
优点:并发收集、低停顿
缺点:产生大量空间碎片、并发阶段会降低吞吐量,还会并发失败
background模式为正常模式执行上述的CMS GC流程
forefroud模式为Full GC模式
//开启CMS垃圾收集器
-XX:+UseConcMarkSweepGC
//默认开启,与-XX:CMSFullGCsBeforeCompaction配合使用
-XX:+UseCMSCompactAtFullCollection
//默认0 几次Full GC后开始整理
-XX:CMSFullGCsBeforeCompaction=0
//辅助CMSInitiatingOccupancyFraction的参数,不然CMSInitiatingOccupancyFraction只会使用一次就恢复自动调整,也就是开启手动调整。
-XX:+UseCMSInitiatingOccupancyOnly
//取值0-100,按百分比回收
-XX:CMSInitiatingOccupancyFraction 默认-1
CMS缺陷:单线程 单核 效率很低
并发失败:导致可中止的预处理 5s
G1
使用G1收集器时,java堆的内存布局与其他收集器有很大差别,它将整个java堆划分为多个大小相等的独立区域(Region),虽然还保留由新生代和老年代的概念,但新生代和老年代不再是物理隔离了,他们都是一部分Region的集合。每个Region大小都是一样的,可以是1M到32M之间的数值,但是必须保证是2的n次幂。
如果对象太大,一个Region放不下[超过Region大小的50%],那么就会直接放到H中,设置Region大小:–XX:G1HeapRegionSize=M,所谓Garbage-First,其实就是优先回收垃圾最多的Region区域。
1.分代收集(仍然保留了分代的概念) 2.空间整合(整体上属于标记-整理算法,不会导致空间碎片) 3.可预测的停顿(比CMS更先进的地方在于能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集器上的时间不得超过N毫秒)
可以分为以下几步
- 初始标记 :标记以下GC Roots能够关联的对象,并且修改TAMS的值,需要暂停用户线程
- 并发标记:从GC Roots进行可达性分析,找出存活的对象,与用户线程并发执行
- 最终标记:修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需要暂停用户线程
- 筛选回收:堆各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间制定回收计划
-XX: +UseG1GC 开启G1垃圾收集器
-XX: G1HeapReginSize 设置每个Region的大小,是2的幂次,1MB-32MB之间
-XX:MaxGCPauseMillis 最大停顿时间
-XX:ParallelGCThread 并行GC工作的线程数
-XX:ConcGCThreads 并发标记的线程数
-XX:InitiatingHeapOcccupancyPercent 默认45%,代表GC堆占用达到多少的时候开始垃圾收集
ZGC
JDK11新引入的ZGC收集器,不管是物理上还是逻辑上,ZGC中已经不存在新老年代的概念了,会分为一个个page,当进行GC操作时会对page进行压缩,因此没有碎片问题。
只能在64位的linux上使用,目前用得还比较少
- 可以达到10ms以内的停顿时间要求
- 支持TB级别的内存
- 堆内存变大后停顿时间还是在10ms以内
垃圾收集器分类
- 串行收集器:Serial和Serial Old 只能有一个垃圾回收线程执行,用户线程停顿
- 并行收集器:Parallel Scanvenge和Parallel Old 多条垃圾收集线程并行工作,但是此时用户线程仍然处于等待状态
- 并发收集器:CMS和G1 用户线程和垃圾收集线程同时执行,垃圾收集线程在执行的时候不会停顿用户线程的运行
常见问题
-
吞吐量和停顿时间
- 停顿时间->垃圾收集器 进行 垃圾回收端应用执行响应的时候
- 吞吐量->运行用户代码时间(运行用户代码时间+垃圾收集时间)
停顿时间越短就越适合需要和用户交互的程序,良好的响应速度能提升用户体验;高吞吐量则可以高效地利用CPU时间,尽量完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
-
如何选择合适的垃圾收集器
- 优先调整堆的大小让服务器自己来选择
- 如果内存小于100M,使用串行收集器
- 如果是单核,并且没有停顿时间要求,使用串行或JVM自己选
- 如果允许停顿时间超过1秒,选择并行或JVM自己选
- 如果响应时间最重要,并且不能超过1秒,使用并发收集器
-
对于G1收集
jdk7开始使用,jdk8非常成熟,jdk9默认的垃圾收集器,适用于新老生代。
是否使用G1收集器:
- 505以上的堆被存活对象占用
- 对象分配和晋升的速度变化非常大
- 垃圾回收时间比较长
-
G1中的RSet
全称Remembered Set,记录维护Region中对象的引用关系
试想,在G1垃圾收集器进行新生代的垃圾收集时,也就是Minor GC,假如该对象被老年代的Region中所引用,这时候新生代的该对象就不能被回收,怎么记录呢?不妨这样,用一个类似于hash的结构,key记录region的地址,value表示引用该对象的集合,这样就能知道该对象被哪些老年代的对象所引用,从而不能回收。
-
如何开启需要的垃圾收集器
-
串行 -XX:+UseSerialGC -XX:+UseSerialOldGC 并行(吞吐量优先) -XX:+UseParallelGC -XX:+UseParallelOldGC 并发收集器(响应时间优先) -XX:+UseConcMarkSweepGC -XX:+UseG1GC
-
工具
jconsole
jconsole工具是JDK自带的可视化工具,查看java应用程序的运行情况、监控堆信息、永久区使用情况、类加载情况等。
命令行中输入:jconsole
jvisualvm
命令行中输入:jvisulavm
visualvm 插件下载地址https://visualvm.github.io/pluginscenters.html
监控本地java进程
可以监控本地的java进程的CPU,类,线程等
监控远端java进程
(1)在visualvm中选中“远程”,右击“添加”
(2)主机名上写服务器的ip地址,比如39.100.39.63,然后点击“确定”
(3)右击该主机"39.100.39.63",添加“JMX”,也就是通过JMX技术具体监控远端服务器哪个Java进程
(4)要想让服务器上的tomcat被连接,需要改一下Catalina.sh这个文件
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote -Djava.rmi.server.hostname=39.100.39.63 -Dcom.sun.management.jmxremote.port=8998-Dcom.sun.management.jmxremote.ssl=false-Dcom.sun.management.jmxremote.authenticate=true-Dcom.sun.management.jmxremote.access.file=../conf/jmxremote.access -Dcom.sun.management.jmxremote.password.file=../conf/jmxremote.password"
(5) 在…/conf文件中添加两个文件jmxremote.access和jmxremote.password
jmxremote.access
guest readonlymanager readwr
jmxremote.password
guest guestmanager manager
(6)将连接服务器地址改为公网ip地址
hostname -i查看输出情况
172.26.225.240 172.17.0.1
vim /etc/hosts
172.26.255.240 39.100.39.63
(7)设置上述端口对应的阿里云安全策略和防火墙策略
(8)启动tomcat,来到bin目录
./startup.sh
(9)查看tomcat启动日志以及端口监听
tail -f ../logs/catalina.out
lsof -i tcp:80
(10)查看8998监听情况,可以发现多开了几个端口
lsof -i:8998 得到PID
netstat -antup | grep PID
(11)在刚才的JMX中输入8998端口,并且输入用户名和密码则登录成功
端口:8998
用户名:manager
密码:manage
arthas
github地址:https://github.com/alibaba/arthas
Arthas 是Alibaba开源的Java诊断工具,采用命令行交互模式,是排查jvm相关问题的利器。
heaphero
https://heaphero.io/
perfma
https://console.perfma.com/
GCViewer
java -jar gcviewer-1.36-SNAPSHOT.jar
gceasy
http://gceasy.io
gcplot
https://it.gcplot.com/
Gc日志分析
要想分析日志的信息,得先拿到GC日志文件才行,所以得先配置一下,根据前面参数的学习,下面的配置很容易看懂。比如打开windows中的catalina.bat,在第一行加上。
XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps
-Xloggc:$CATALINA_HOME/logs/gc.log
这样使用startup.bat启动tomcat的时候就能够在当前目录下拿到gc.log文件,可以看到默认使用的是ParallelGC。
性能优化
内存分配
正常情况下不需要设置,那如果是促销或者秒杀的场景呢?
每台机器配置2c4G,以每秒3000笔订单为例,整个过程持续60秒
内存溢出
大并发情况下
内存泄露导致内存溢出
大并发[秒杀]
浏览器缓存、本地缓存、验证码
CDN静态资源服务器
集群+负载均衡
动静态资源分离、限流[基于令牌桶、漏桶算法
]应用级别缓存、接口防刷限流、队列、Tomcat性能优化
异步消息中间件
Redis热点数据对象缓存
分布式锁、数据库锁
5分钟之内没有支付,取消订单、恢复库存等
CPU占用率高
-
top
-
top -Hp PID
查看进程中占用CPU高的线程id,即tid
-
jstack PID | grep tid