这张是jdk8的jvm模型:
黄色框的是线程共享区域、蓝色框的是线程私有(也就是每个线程单独一份)
jvm模型从大的角度说有:类装载子系统、字节码执行引擎、运行时数据区。我这里主要讲运行时数据区。
堆:
其唯一的用途就是存放对象实例:所有的对象实例及数组都在堆上进行分配。包含:新生代(Eden区、S0、S1)、老年代。官方推荐配置为年轻代大小占整个堆的3/8。-XX:NewRatio=3/5表示新生代和老年代的比值, 而Eden:S0:S1=8:1:1
虚拟机栈
描述的是java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame),用于存储 局部变量、操作数栈、动态链接、方法出口等信息.生命周期与线程相同。栈里面存放着各种基本数据类型和对象的引用
如果在栈帧中有一个变量,类型为引用类型,比如Object obj=new Object(),这时候就是典型的栈中元素指向堆中的 对象
方法区(元空间):
存储已被虚拟机加载的类信息。jdk8的JVM不再有永久代(PermGen),原永久代存储的信息被分成两部分: 1、虚拟机加载的类信息(放在元空间) 2、运行时常量池(放在堆中)
元空间和方法区:第一个是hotspot的具体实现技术,第二个是JVM规范的抽象定义,不能说元数据区就是方法区,但可以说元空间用来实现了方法区
方法区中会存放静态变量,常量等数据。如果是下面这种情况,就是典型的方法区中元素指向堆中的对象。private static Object
obj=new Object();
本地方法栈:
本地方法栈则为虚拟机使用到的Native方法服务(非java代码的接口,比如C++的方法:Runtime.getRuntime().exec()是执行shell脚本的命令)
程序计数器:
当前线程执行的字节码的行号指示器;各线程之间独立存储,互不影响
查看当前服务器用的垃圾回收器:java -XX:+PrintCommandLineFlags -version
标记:
找出内存中需要回收的对象,并且把它们标记出来
清除:
清除掉被标记需要回收的对象,释放出对应的内存空间
缺点:
标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
(1)标记和清除两个过程都比较耗时,效率不高
(2)会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无 法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
将内存划分为两块相等的区域,每次只使用其中一块,如下图所示
当其中一块内存使用完了,就将还存活的对象复制到另外一块上面,然后把已经使用过的内存空间一次 清除掉
缺点:
空间利用率降低。
标记过程仍然与"标记-清除"算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活
的对象都向一端移动,然后直接清理掉端边界以外的内存,让所有存活的对象都向一端移动,清理掉边界意外的内存。
Young区:复制算法(对象在被分配之后,可能生命周期比较短,Young区复制效率比较高)
Old区:标记清除或标记整理(Old区对象存活时间比较长,复制来复制去没必要,不如做个标记再清理)
复制
:发生在新生代(优点:无内存碎片,效率高 缺点:需要两倍的空间)
标记清理
:发生在老年代(优点:占用空间少 缺点:会产生内存碎片)
标记整理
:发生在老年代(优点:占用空间少,无碎片 缺点:对象移动,会消耗资源)
1、Serial:串行(-XX:+UseSerialGC)>为单线程环境设计,且使用一个线程回收垃圾,会暂停所有的用户线程,不适合服务器环境(例如:用户用餐,餐厅叫出去要叫一个清洁工打扫,打扫完再回来吃)
2、Parallel:并行(-XX:+UseParallelGC)>多个并行垃圾收集线程工作,此时用户线程是暂停的,适用于科学计算、大数据处理首台处理等若交互环境(例如:用户用餐,餐厅叫出去要叫多个清洁工打扫,打扫完再回来吃)
3、CMS:(-XX:UseConcMarkSweepGC)>用户线程和垃圾收集线程同时执行(并不一定是并行,可能交替执行),不需要停顿用户线程
,适用对响应时间有要求的场景(例如:用户用餐,餐厅叫出去要叫多个清洁工打扫,边吃边打扫)
采用的是"标记-清除算法",整个过程分为4步:
(1)初始标记 CMS initial mark 标记GC Roots能关联到的对象 Stop The World-- ->速度很快
(2)并发标记 CMS concurrent mark 进行GC Roots Tracing
(3)重新标记 CMS remark 修改并发标记因用户程序变动的内容 Stop The World
(4)并发清除 CMS concurrent sweep
4、G1:(garbage first)(-XX:UseG1GC)>G1垃圾回收器将堆内存分割成不通的区域然后并发的对其进行垃圾回收 java11默认GC回收>> 器是ZGC。属于标记-整理
算法
使用G1收集器时,Java堆的内存布局与就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合
(1) 初始标记(Initial Marking) 标记一下GC Roots能够关联的对象,并且修改TAMS的值,需要暂 停用户线程
(2)并发标记(Concurrent Marking) 从GC Roots进行可达性分析,找出存活的对象,与用户线程并发 执行
(3)最终标记(Final Marking) 修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需 暂停用户线程
(4)筛选回收(Live Data Counting and Evacuation) 对各个Region的回收价值和成本进行排序,根据 用户所期望的GC停顿时间制定回收计划
判断一个对象是否可达,不可达对象就将被回收,所谓可达就是从GCROOT开始是否是可以找到该对象 GCROOT是什么?
1、虚拟机栈中引用的对象(本地变量表)
2、方法区中静态属性引用的对象
3、方法区中常量引用的对象
4、本地方法栈中引用的对象(Native Object)
(1)串行
-XX:+UseSerialGC -XX:+UseSerialOldGC
(2)并行(吞吐量优先):
-XX:+UseParallelGC -XX:+UseParallelOldGC
(3)并发收集器(响应时间优先)
-XX:+UseConcMarkSweepGC -XX:+UseG1GC
例如:java -server -Xms1024m -Xmx1024m -XX:MetaspaceSize=1024m -XX:+PrintFlagsFinal -XX:+UseG1GC -jar springboot2019-SNAPSHOT.jar
1.对Eden内存空间进行清理,即垃圾收集(Garbage Collect),这样的GC我们称之为
Minor GC
2.Old区的GC我们称作为Major GC
3.新生代(Minor GC)+老年代(Major GC)Full GC
老年代采取的垃圾回收算法是
标记整理
算法 老年代触发垃圾回收的机制,一般就是两个;
①在Minor GC之前
,一通检查发现很可能Minor GC之后要进入老年代的对象太多了,老年代放不下, 此时需要提前触发Full
GC再然后再带着进行Minor GC;
②在Minor GC之后
,发现剩余对象太多放入老年代都放不下了
1、当对象的年龄达到15岁时
默认的设置下,也就是躲过15次GC的时候,他就会转移到老年代里去 具体是多少岁进入老年代,可以通过JVM参数“-XX:MaxTenuringThreshold”
来设置,默认是15岁
2、动态对象年龄判断
假如说当前放对象的Survivor区域里,一批对象的总大小大于了这块Survivor区域的内存大小的50%,
那么此时大于等于这批对象年龄的最大值对象
,就可以直接进入老年代了
例如:年龄1+年龄2+年龄n,的多个年龄对象总和超过了Survivor区的50%,此时就会把年龄n以上的对象都放入老年代
3.大对象直接进入老年代
如果你要创建一个大于这个大小的对象,比如一个超大的数组,或者是别的啥东西,此时就直接把这个大对象放到老年代里去,压根不会经过年轻代,有一个JVM参数,就是“-XX:PretenureSizeThreshold”
,可以把它的值设置为字节数,比如“1048576”字节,就是1MB
4.Minor GC后的对象太多
Minor GC后的对象太多无法放入Survivor区 这个时候就必须得把这些对象直接转移到老年代去
强引用:>只要还有强引用指向对象,就算OOM也不会回收该对象。Object o1=new Object(),就算强引用
软引用:>内存足够的情况不回收,内存不足时就回收的对象。SoftReference,用于内存敏感的地方
Object o1=new Object();
SoftReference sf = new SoftReference<>(o1);
弱引用:>不管内存是否够用,GC时一律回收 Object o1=new Object(); WeakReference sf = new WeakReference<>(o1);
虚引用:>PhantomReference 虚引用并不会决定生命周期,如果一个对象仅持有虚引用,那么他就和没有引用是一样的,任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列联合使用
主要作用是:跟踪对象被垃圾回收的状态
①标配参数
例如:java -version/java -help /java -showversion
②x参数
java -Xint解释执行(java -Xint -version)、java -Xcomp第一次使用就编译成本地代码、java -Xmixed混合模式
③XX参数
-XX:+或者-某个属性值,其中+表示开启、-表示关闭 例如:jinfo -flag PrintGCDetails 进程号 ===>>>如果出现-XX:-PrintGCDetails 代表关闭了GC回收参数配置,如果是-XX:+PrintGCDetails 带表已配置了GC回收参数
④其他参数(所以这块也相当于是-XX类型的参数)
-Xms1000等价于-XX:InitialHeapSize=1000
-Xmx1000等价于-XX:MaxHeapSize=1000
-Xss100等价于-XX:ThreadStackSize=100
例如一个设置内存的示例: java -server -Xms1024m -Xmx1024m -XX:+UseG1GC -jar springboot2019-SNAPSHOT.war
总结公式:java -server jvm的各种配置参数 -jar jar包或者war包的名字
单位换算
1Byte(字节)=8bit(位)
1KB=1024Byte(字节)
1MB=1024KB
1GB=1024MB
1TB=1024GB
jvm常用参数含义
设置jvm调优的两种方法:
①在tomcat的bin下面的catalina.sh 里,位置cygwin=false前JAVA_OPTS=‘-server -Xms512m -Xmx768m -XX:NewSize=128m -XX:MaxNewSize=192m -XX:SurvivorRatio=8’
②使用jar包启动的话java -server -Xms1024m -Xmx1024m -XX:+UseG1GC -jar springboot2019-SNAPSHOT.jar
war包也可以这样:java -server -Xms1024m -Xmx1024m -XX:+UseG1GC -jar springboot2019-SNAPSHOT.war
参数解释:
-Xms 初始堆内存大小
-Xmx 最大堆内存大小
-Xss 单个线程栈大小
-XX:NewSize 初始新生代堆大小
-XX:MaxNewSize 生代最大堆大小
-XX:MetaspaceSize 元数据区初始值(JDK1.8)
-XX:MaxMetaspaceSize 元数据区最大值(JDK1.8)
-XX:SurvivorRatio 用来设置新生代中eden空间和from/to空间的比例.
含以-XX:SurvivorRatio=eden/from=den/to
总结:在实际工作中,我们可以直接将初始的堆大小与最大堆大小相等, 这样的好处是可以减少程序运行时垃圾回收次数,从而提高效率。
例如:linux设置tomcat的catalina.sh
JAVA_OPTS=-Xms1024m -Xmx1024m -Xss1m -XX:MetaspaceSize=128m -XX:MAXMetaspaceSize=256m -XX:NewSize=256m -XX:MaxNewSize=256m
或者:java -server -Xms1024m -Xmx1024m -Xss1m -XX:MetaspaceSize=128m -XX:MAXMetaspaceSize=256m -XX:NewSize=256m -XX:MaxNewSize=256m -jar springboot2019-SNAPSHOT.jar
调整完查看,打印JVM所有参数列表的方法:
java -XX:+PrintFlagsFinal -version
查看JVM初始化参数:java -XX:+PrintFlagsInitial
①执行 jps
:(Java Virtual Machine Process Status
Tool)是java提供的一个显示当前所有java进程pid的命令,适合在linux/unix平台上简单察看当前java进程的一些简单情况
②查看当前进程有哪些参数
: 打印命令行参数 jinfo -flags 进程号。 ==>>例如打印GC信息:jinfo -flags
例如,打印指定进程全部参数:jinfo -flags 14857
例如,打印指定进程指定参数内容:jinfo -flag PrintGCDetails 14857
查看
格式:
jinfo -flag name PID
查看某个java进程的name属性的值
jinfo -flags PID
查看某个java进程的全部属性的值
jinfo -flag MaxHeapSize PID
jinfo -flag UseG1GC PID
例如:
jinfo -flags 13573
*重点*
jinfo -flag MaxHeapSize 13573
jinfo -flag UseG1GC 13573
jstat命令可以查看堆内存各部分的使用量,以及加载类的数量
jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]
格式:
jstat -class PID 1000 10
查看某个java进程的类装载信息,每1000毫秒输出一次,共输出10次
例如:
查看类装载信息:jstat -class 9939
垃圾回收统计:jstat -gc 9939
格式:jstack PID
Jstack 用jstack加进程id查找死锁(查找递归,死循环等得位置)
行
jstack >19663|grep 4cd0 -A60
//19663是进程号,4cd0是十六进制的线程号,-A60是打印错误附近的60行代码
打印出堆内存相关信息:
jmap -heap PID
例如:jmap -heap 2304
jmap 用来查看内存信息,实例个数以及占用内存大小
jmap ‐histo 14660
#查看历史生成的实例,14660是进程id用jps查 jmap ‐histo:live 14660 #查看当前存活的实例,,14660是进程id用jps查。执行过程中可能会触发一次full gc 堆信息:jmap -heap 14660 也可以设置内存溢出自动导出dump文件(内存很大的时候,可能会导不出来)
- -XX:+HeapDumpOnOutOfMemoryError
- -XX:HeapDumpPath=./ (路径)
例如:
‐Xms10M ‐Xmx10M ‐XX:+PrintGCDetail‐XX:+HeapDumpOnOutOfMemoryError ‐XX:HeapDumpPath=D:\jvm.dump
jmap命令查看堆内存jmap -heap 进程ID
查看内存使用情况jmap -heap 9939 jmap ‐histo
| more
例如: 查看内存中对象数量及大小
jmap -histo:live 11927 | more
①错误:java.lang.OutOfMemoryError:Metaspace
元空间大小内存溢出?
设置元空间大小方法:-XX:MetaspaceSize=1024m* 就是设置元空间的大小
实战中设置元空间大小:
java -server -Xms1024m -Xmx1024m -XX:MetaspaceSize=1024m -XX:+PrintFlagsFinal -jar springboot2019-SNAPSHOT.jar
元空间的本质和永久代类似,都是对JVM规范中的方法区的实现。不过元空间与永久代的最大区别是:元空间并不在虚拟机中,而是使用本地内存,默认情况下元空间仅受本地内存大小限制
查看jvm所有配置信息:java -XX:+PrintFlagsFinal -version
这样找到MetaspaceSize对应的大小是21807104 字节 换算过来才 20.79m。也就是说元空间虽然只跟本地内存有关,但它初始值只有21m,可以调大
②栈空间的大小调整?
-Xss等价于:-XX:ThreadStackSizejava -XX:+PrintFlagsFinal -version查找ThreadStackSize是栈空间大小,会发现初始值为0
栈空间默认值跟平台有关:
Linux(x64):1024kb
OS X(64-bit):1024kb
Windows:虚拟内存的默认值
例如:java -server -Xss128k -jar springboot2019-SNAPSHOT.jar
③查看垃圾回收多少次后才会由新生代进入老年代?
3.1、查看程序进程 jps -l
3.2、查看默认垃圾回收次数,才进入老年代 jinfo -flag
MaxTenuringThreshold 进程号 会看到默认是15次(最大设置为15)
1、使用 top查看占用cpu高的程序
例如:top
假如,cpu占用最高的就是 elasticsearch (一般是业务jar包)
37 root 20 0 0 0 0 S 0.3 0.0 0:01.82 elasticsearch
2、使用jps或ps -ef|grep “elasticsearch” 去找出这个占用高的程序的进程号
例如:jps -l
1541 Elasticsearch
3、定位到具体的线程或代码:ps -mp 进程 -o THREAD,tid,time
-m
:显示所有线程
-p
: pid进程使用cpu时间
-o
:该参数后是用户自定义格式
例如:ps -mp 1541 -o THREAD,tid,time
输出很多(最后一列是时间,假如这个线程耗时最长):esuser 0.0 19 - futex_ - - 1541 00:00:08
找到时间最长的那个线程号
4、把需要的线程id
转换为16进制
的格式(要英文小写
的)
方法一:执行命令:printf “%x\n” 有问题的线程id。例如:printf "%x\n" 2242
输出:8c2
方法二:用计算器转换为16进制
5、执行命令:jstack 进程ID |grep tid(16进制的线程id英文小写) -A60
例如:jstack 2242 |grep 8c2 -A60
tid(16进制的线程id英文小写):是一个整体是指上面换算后的线程id(要16进制那个值
)
-A60是指打印最近的60行
在打印信息中找到包名就是java代码对应的哪一行报出来的错!
1.装载(Load)
查找和导入class文件
(1)通过一个类的全限定名获取定义此类的二进制字节流
(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
(3)在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口
2.链接(Link)
保证被加载类的正确性
文件格式验证
元数据验证
字节码验证
符号引用验证
为类的静态变量分配内存,并将其初始化为默认值
把类中的
符号引用
转换为直接引用
符号引用(Symbolic References)
:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。在Class文件中就是这种
直接引用
:直接引用可以是 1、直接指向目标的指针 2、相对偏移量 3、一个能间接定位到目标的句柄
3 初始化(Initialize)
对类的静态变量,静态代码块执行初始化操作
4 使用
5 卸载
当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中, 只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的 Class),子类加载器才会尝试自己去加载。 采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就
保证了使用不同的类加载 器最终得到的都是同样一个 Object 对象
。