JVM学习笔记
2023-06-08 15:30:40 0 举报
AI智能生成
jvm功能详解
作者其他创作
大纲/内容
PC寄存器
1、CPU需要频繁的切换各个线程,这个时候切换回来以后,就需要知道接着从哪里开始执行
2、JVM字节码解释器需要通过PC寄存器的值,来明确下一条应该执行什么样的字节码指令
3、没有GC和内存溢出,线程独享,为了能够准确的记录各个线程正在执行的当前字节码指令地址
java栈
栈是运行时的单位,堆是存储的单位
栈解决程序的运行问题,即程序如何运行,线程私有的;
堆解决数据存储问题,数据怎么放,放在哪儿;
虚拟机栈基本内容
每个线程在创建的时候,都会创建一个虚拟机栈,其内部保存一个个栈帧,对应一次次的java方法调用
生命周期和线程一致
主管java程序的运行,保存方法的局部变量(8种数据类型,对象的引用地址)、部分结果,并参与方法的调用和返回
栈的优点
快速有效的分配存储方式,访问速度仅次与程序计数器
jvm直接对栈的操作
每个方法执行伴随着进栈(入栈、压栈)
执行结束后的出栈工作
不存在垃圾回收问题
异常
java虚拟机规范允许java栈的大小是动态的或者固定不变的
如果固定,当栈空间不足的时候,就会抛出StackOverflowError
如果动态,当尝试扩展栈空间,没有足够的内存的时候,就会抛出OutOfMemoryError
栈大小设置
参数-Xss设置线程的最大栈空间,栈的大小直接决定函数调用的最大可达深度
-Xss256M
分配的栈内存越大越好吗?
理论来说越大,拉长StackOverflowError出现的时间,占用其他的操作空间
垃圾回收会涉及到虚拟机栈吗?
不会的,存在ERROR,不存在GC
方法中定义的局部变量是否存在线程安全问题
具体问题具体分析
如果只有一个线程才可以操作此数据,那必定是线程安全的
如果多个线程操作此数据,那么这个数据是共享的,如果不考虑同步机制,会存在此线程安全的问题
栈帧
每个线程都有自己的栈,栈中的数据都以栈帧(Stack Frame)的格式存在,在这个线程上正在执行的每个方法都各自对应着一个栈帧
栈帧是一块内存区域,是一个数据集,维系着方法执行过程中的各种数据信息
JVM对java栈的操作只有两个,就是对栈帧压栈/出栈,遵循先进后出/后进先出的原则
在一条活动线程上,一个时间点,只会有一个活动的栈帧
只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的——当前栈帧
与当前栈帧对应的方法——当前方法
定义这个方法的类——当前类
如果该方法调用了其他方法,对应新的栈帧会被创建出来,放在栈的顶端,成为新的当前栈帧;
调用的方法返回的时候,当前栈帧会传回此方法的执行结果给前一个栈帧,接着JVM会丢弃当前栈帧,使得前一个栈帧成为当前栈帧;
java方法有两种返回函数的方式,一种是正常的返回,通过Return,另一种是抛出异常,不管哪种都会导致栈帧被弹出。
调用的方法返回的时候,当前栈帧会传回此方法的执行结果给前一个栈帧,接着JVM会丢弃当前栈帧,使得前一个栈帧成为当前栈帧;
java方法有两种返回函数的方式,一种是正常的返回,通过Return,另一种是抛出异常,不管哪种都会导致栈帧被弹出。
执行引擎运行的所有字节码指令只针对当前栈帧进行操作
不同线程中所包含的栈帧是不允许相互引用的
栈中能发多少栈帧,取决于栈帧的大小,栈帧的大小取决于栈内的局部变量表和操作数据等
组成
局部变量表
也被称之为本地变量表和局部变量数组
定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,
这些数据类型主要包括基本数据类型,引用类型(reference)、returnAddress类型
这些数据类型主要包括基本数据类型,引用类型(reference)、returnAddress类型
线程独有,不存在数据安全问题(数据安全主要值在线程共享导致的数据安全)
局部变量表所需要的的容量大小是在编译期确定下来的,在方法运行期间是不会改变局部变量表的大小,
并保存在方法的code属性的Maximum Local variables数据项中
并保存在方法的code属性的Maximum Local variables数据项中
方法嵌套的次数由栈的大小决定,一般来说,栈越大,方法嵌套调用次数越多;
对于一个函数而言,他的参数和局部变量越多,使得局部变量表越膨胀,他的栈帧就越大,
以满足方法调用所需传递的信息增大的需求,进而函数调用就会占用更多的栈空间,导致其嵌套调用的次数越小
对于一个函数而言,他的参数和局部变量越多,使得局部变量表越膨胀,他的栈帧就越大,
以满足方法调用所需传递的信息增大的需求,进而函数调用就会占用更多的栈空间,导致其嵌套调用的次数越小
局部变量表中的变量只在当前方法调用中有效;
在方法执行开始,JVM就会利用局部变量表完成参数值到局部变量列表的传递过程;
当方法调用结束后,随着方法栈帧的销毁,局部变量表也随之销毁;
在方法执行开始,JVM就会利用局部变量表完成参数值到局部变量列表的传递过程;
当方法调用结束后,随着方法栈帧的销毁,局部变量表也随之销毁;
局部变量表最基本的存储单元-Slot
局部变量表中,32位以内的类型值只占用一个slot(包括retunrAddress),64位的类型(long、double)占用2个slot
JVM会为局部变量表中的每一个Slot都分配一个访问索引,通过这个索引可成功访问到局部变量表中指定的局部变量值
当一个实例方法被调用的时候,他的方法参数和方法体内定义的局部变量,将会按照顺序被复制到局部变量表中的每一个slot上
如果要访问局部变量表中一个64位的局部变量值,只需要使用前一个即可(占用两个slot,用第一个solt的索引)
如果当前栈帧是由构造方法或者实例方法创建的,那么该对象引用this将会存放在index为0的slot处,其余的参数按照参数表顺序继续排列
这就是为什么在类内,static修饰的方法不能用this,而非static的方法内可以使用this
这就是为什么在类内,static修饰的方法不能用this,而非static的方法内可以使用this
栈帧中局部变量表中的slot可以重用的,局部变量过了作用域,那么其作用域之后申明的新的局部变量,就可能复用过期局部变量的slot,从而达到节省资源的目的
静态变量和局部变量的对比
静态变量
静态变量表有两次初始化的机会,第一个是链接中的准备阶段,执行系统初始化,对类变量设置0值,另一次是初始化阶段,赋予程序员在代码中定义的初始值
局部变量
局部变量表不存在系统初始化的过程,一旦定义局部变量必须人为的初始化,否则无法使用
在栈帧中,与系统调优最为密切的部分是局部变量表,在方法执行的时候,JVM通过局部变量表完成方法的传递;
局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或者间接的引用的对应都不会被回收;
局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或者间接的引用的对应都不会被回收;
操作数栈
后进先出,也称之为表达式栈
在方法执行的过程中,根据字节码指令,往操作数栈中写入数据和提取数据,即入栈(push)和 出栈(pop)
某些字节码指令将值压入操作数栈,其他的字节码指令将操作数取出栈,使用他们后再把结果压入栈;
例如:复制,求和,交换等操作
例如:复制,求和,交换等操作
JVM的解释引擎是基于操作数栈的执行引擎
作用
主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间
JVM执行引擎的一个临时工作区,当一个方法刚开始执行的时候,一个新的栈帧也会被创建出来,这个方法的操作数栈是空的,
每一个操作数栈都会拥有一个明确的栈深度用于存储数值,其所需的最大深度在编译期就定义好了,保存在方法的code属性中,为max_stack的值
栈中的任何一个元素都是可以任意的java数据类型
操作数栈并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈和出栈操作完成一次数据访问
动态链接
指向运行时常量池的方法引用,例如:invokedynamic指令
例如:描述一个方法调用另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,
那么动态链接的作用就是为了将符号引用转换为调用方法的直接引用
那么动态链接的作用就是为了将符号引用转换为调用方法的直接引用
常量池的作用:就是为了提供一些符号和常量,便于指令的识别
方法返回地址
存放调用改方法的PC寄存器的值
异常退出和正常退出
一些附加信息
方法的调用
JVM中,将符号引用转换为调用方法的直接引用,与方法的绑定机制相关
方法的链接
动态链接
如果被调用的方法在编译期无法确定,只能够在程序运行期将调用的方法符号引用转换为直接引用,
由于这种引用转换过程具备动态性,因此称之为动态链接
由于这种引用转换过程具备动态性,因此称之为动态链接
静态链接
当一个字节文件被转载到JVM时,如果被调用的目标方法在编译期就可知,且运行期保持不变,
这种情况下将调用方法的符号引用转换成直接引用的过程,称之为静态链接
这种情况下将调用方法的符号引用转换成直接引用的过程,称之为静态链接
方法的绑定机制
绑定是一个字段、方法或者类的符号引用转换为直接引用的过程,这仅仅发生一次
早起绑定
被调用的目标方法,如果在编译期可知,且运行期保持不变
晚期绑定
被调用的目标方法,如果在编译期无法确定下来,只能在程序运行期间根据实际的类型绑定相关的方法
虚方法和非虚方法
非虚方法
如果方法在编译期间就确定了具体的调用版本,这个版本在运行时是不可变的;
静态方法,私有方法,final修饰的方法,父类方法,实例构造器都是非虚方法
静态方法,私有方法,final修饰的方法,父类方法,实例构造器都是非虚方法
invokestatic、invokespecial指令调用的方法成为非虚方法,其余的(非final修饰的方法)是虚方法
虚方法
invokevirtual、invokeinterface指令修饰的方法
虚方法表
方法重写的本质
在面向对象编程中,会频繁使用动态分派,如果在每次的动态分类过程中,都要重新在类的方法元数据中搜索元数据的话,
就会影响执行效率,因此为了提高执行效率,JVM采用在类的方法区建立一个虚方法表,通过索引表代替查找
就会影响执行效率,因此为了提高执行效率,JVM采用在类的方法区建立一个虚方法表,通过索引表代替查找
每个类中都有一个虚方法表,表中存放着各个方法的实际入口
在类加载的链接阶段被创建并开始初始化,类的变量初始值准备完毕后,JVM会把类的方法表也初始化完毕
本地方法栈
1、java栈用于管理java方法的调用,本地方法栈用于管理本地方法的调用;
2、线程私有
3、与java栈拥有相同的特性,可固定大小,也可动态扩展;
4、本地方法使用C实现
5、在Native Method Stack中登记native方法,在Execute engine执行时加载本地方法库
6、并不是所有的JVM都支持本地方法;
2、线程私有
3、与java栈拥有相同的特性,可固定大小,也可动态扩展;
4、本地方法使用C实现
5、在Native Method Stack中登记native方法,在Execute engine执行时加载本地方法库
6、并不是所有的JVM都支持本地方法;
堆
一个JVM实例只存在一个堆内存,堆也是java内存管理的核心区域
1.8以后无永久代,改为元空间,字符串常量池、静态变量存储在堆中,类型信息、域(字段)、方法、运行时常量池保存在本地内存方法区(元空间)
java堆区在JVM启动的时候即被创建,其空间大小也就确定了,是JVM管理的最大一块内存空间
堆内存大小是可以调节的,在JVM启动的时候设置启动参数
-Xms10m -Xmx10m
-Xms10m:堆区的起始内存(新生代和老年代)
-Xmx10m:堆区的最大内存(新生代和老年代)
-Xms10m:堆区的起始内存(新生代和老年代)
-Xmx10m:堆区的最大内存(新生代和老年代)
一旦堆区的内存大小超过-Xmx所指定的最大内存时,将会抛出OutOfMemoryError
通常将-Xms和-Xmx大小配置相同,目的是在JVM垃圾回收机制清理完堆后,不需要重新分隔计算堆区的大小,提高性能
默认情况下,初始内存大小=电脑物理内存大小的1/64
最大内存大小=电脑物理内存大小的1/4
最大内存大小=电脑物理内存大小的1/4
查看设置的参数
jps :查询进程ID
jstat
jstat -gc 进程ID : 查询进程ID使用的内存情况(进程ID通过jps命令查询到的)
S0C:第一个幸存区的大小
S1C:第二个幸存区的大小
S0U:第一个幸存区的使用大小
S1U:第二个幸存区的使用大小
EC:伊甸园区的大小
EU:伊甸园区的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法区大小(元空间)
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
S1C:第二个幸存区的大小
S0U:第一个幸存区的使用大小
S1U:第二个幸存区的使用大小
EC:伊甸园区的大小
EU:伊甸园区的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法区大小(元空间)
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
jstat -gccapacity 3676(进程ID)
堆内存统计
jstat -gcnew 3676
新生代垃圾回收统计
jstat -gcnewcapacity 3676
新生代内存统计
jstat -gcold 3676
老年代垃圾回收统计
-XX:+PrintGCDetails : 打印垃圾回收的细节
新生代和老年代堆空间占比
默认新生代1/3,老年代2/3
也可以使用-Xmn100M显式的设置新生代最大内存大小,如果同时设置了-XX:NewRatio=2,那么以-Xmn设置的为准
-XX:NewRatio=2 表示新生代1/3,老年代2/3;
-XX:NewRatio=4 表示新生代1/5,老年代4/5;
-XX:NewRatio=4 表示新生代1/5,老年代4/5;
jinfo -flag NewRatio 进程ID 查看进程新生代和老年代的比例
新生代中Eden和Survivor堆空间比例
官方网站上面说是:8:1:1,但是在实际应用中是6:1:1(实测),除非通过-XX:SurvivorRatio=8实际进行设置,才是8:1:1
-XX:SurvivorRatio=8 设置新生代中Eden和Survivor堆空间比例
-XX:-UseAdaptiveSizePolicy 关闭自适应比例(-关闭,+打开)
所有的线程都共享堆,在这里还可以划分私有的线程私有的缓冲区(Thread Local Allocation Buffer, TLAB )
堆空间不是所有的空间都是线程共享的
几乎所有的对象实例和数组都应该在运行时分配在堆上,栈帧中保存引用地址,这个引用地址指向对象实例和数组在堆中的具体位置;
在方法结束后,堆中的对象实例或者数组并不会马上移除,仅仅在垃圾回收的时候才会移除;
堆是GC执行垃圾回收的重点区域;
在方法结束后,堆中的对象实例或者数组并不会马上移除,仅仅在垃圾回收的时候才会移除;
堆是GC执行垃圾回收的重点区域;
内存细分(分代收集理论)1.8及以后
新生代/新生区
伊甸园
几乎多有的对象都是在Eden区new出来的,在Eden对象空间允许的范围
survivor0/1区(from/to)
当空间满了不会触发Young GC,当Eden满了会触发Young GC,同时回收Eden区和survivor0/1区对象,Survivor0和Survivor1总是有一块区域是空的
动态对象年龄判断
如果survivor0/1区(from/to)区中年龄相同的对象所占的空间大于等于survivor0/1区大小的一半,年龄大于等于这个年龄的对象,无需等到MaxTenuringThreshold的值,可直接转入老年代
Survivor0和Survivor1,复制以后有交换,谁空谁是to
垃圾回收:频繁在新生代收集,很少在老年代收集,几乎不再老年代或者元空间收集
绝大部门的java对象都是在新生代销毁(80%)左右
养老代/老年代
XX:MaxTenuringThreshold=10 设置对象从Survivor转Old Area次数存活阈值,默认15次Young GC/Minor GC还存活的对象就会转老年代
元空间/永久代
分区的主要目的
也可以不分区,分区的目的是:优化GC性能
垃圾回收器
Minor GC
只是新生代垃圾收集
触发机制
当Eden满的时候会触发Minor GC,放Survivor0/1满的时候,不会触发Minor GC,当Minor GC触发后会回收Eden和Survivor0/1区的垃圾
因为Java对象的朝生夕死的特性,所以Minor GC会非常频繁,回收速度非常快
Minor GC会引起STW,暂停其他用户线程,等待垃圾回收结束,用户线程才能恢复
Major GC
只是老年代垃圾收集
出现Major GC通常会伴随着至少一次Minor GC,也就是老年代空间不足的时候,先会尝试触发Minor GC,如果空间还是不足,就会触发Major GC
Major GC的时长通常是Minor GC的10倍以上,STW时间会更长,如果Major GC后空间还是不足,就会报OOM
Full GC
收集整个java堆和方法区的垃圾收集
调用System.gc(),系统建议执行Full GC,但是非必然执行
老年代空间不足或者方法区空间不足
Major GC后进入老年代的大小大于老年代可用的空间
由Eden区和Survivor(from)区,复制到Survivor(to)区,Survivor(to)区的空间不足,然后Survivor(to)区向老年代转,但是老年代的空间不足
开发和调试中应尽量避免Full GC,这样STW的时间会短一些
在出现OOM之前通常会伴随着Full GC
很多时候Major GC和Full GC混淆使用,具体需要分辨是老年代回收还是整堆回收
1、大部分垃圾回收都是在新生代Minor GC;
2、大部分调优,都是GC调优就是希望垃圾回收次数少一些,因为垃圾回收会导致用户线程暂停,出现STW(stop the world),重点调优Major GC和Full GC
2、大部分调优,都是GC调优就是希望垃圾回收次数少一些,因为垃圾回收会导致用户线程暂停,出现STW(stop the world),重点调优Major GC和Full GC
TLAB( thread-local allocation block)
原因
由于堆区线程共享,对象的创建在JVM非常频繁,因此在并发的情况下,从堆中划分内存空间是线程不安全的;
为了避免多个线程操作同一个地址,需要考虑加锁的机制,进而影响分配速度。
为了避免多个线程操作同一个地址,需要考虑加锁的机制,进而影响分配速度。
从内存模型的角度而不是垃圾回收的角度,对Eden区域进行进一步划分,JVM为每个线程分配了一个私有缓冲区,它包含在Eden空间内
好处
从内存模型的角度而不是垃圾回收的角度,对Eden区域进行进一步划分,JVM为每个线程分配了一个私有缓冲区,它包含在Eden空间内
多线程同时分配内存的时候,使用TLAB避免了线程安全问题,提升内存分配的吞吐量,称这种分配策略为快速分配策略
OpeJdk衍生出的JVM都提供了TLAB支持
说明
默认情况下,TLAB占用Eden的内存为1%,可以使用-XX:TLABSize=512k,设置TLAB占用Eden的大小;
不是所有的对象实例都能在TLAB中分配内存大小,JVM确实将TLAB作为内存分配的首选;
一旦对象在TLAB中分配内存失败,JVM就会尝试使用加锁机制来保证数据操作的原子性,从而直接在Eden空间中分配内存;
不是所有的对象实例都能在TLAB中分配内存大小,JVM确实将TLAB作为内存分配的首选;
一旦对象在TLAB中分配内存失败,JVM就会尝试使用加锁机制来保证数据操作的原子性,从而直接在Eden空间中分配内存;
程序开发人员可以通过:-XX:+UseTLAB 查看TLAB是否开启;
例如:jps ;查看进程号
jinfo -flag UseTLAB 进程ID;
例如:jps ;查看进程号
jinfo -flag UseTLAB 进程ID;
堆空间所有空间都是共享的嘛?
在Eden区中有线程独享的TLAB空间
常用参数
-XX:+PrintFlagsInitial
查看所有参数的默认初始值
-XX:+PrintFlagsFinal
查看所有参数的最终值,修改过后和初始值不同
-Xms
初始堆空间内存,默认为物理内存的1/64
-Xmx
最大堆空间内存,默认为物理内存的1/4
-Xmn
设置新生代的大小,初始值及最大值
-XX:NewRatio
设置新生代和老年代在堆空间中的占比,默认是1/2
-XX:SurvivorRatio
设置Eden和Survivor0/1在新生代中的占比,默认值是8:1:1,在未显式指定的情况下是6:1:1
如果Survivor0/1区域过小(满了不会触发Minor GC),就会向老年代去转,导致老年代区域数据变多,失去了Minor GC的效果,增加了Major GC压力
如果Survivor0/1区域过大,由于其满了不会触发Minor GC,就是导致Minor GC过于频繁,就会出现过多STW,阻碍用户线程的执行
-XX:MaxTenuringThreshold
设置新生代垃圾的最大年龄,默认值15
-XX:+PrintGCDetails
输出详细的GC处理日志
-XX:HandlePromotionFailure
是否设置空间分配担保,在jdk7以后-XX:HandlePromotionFailure不会影响虚拟机的空间分配担保策略,源码中写死就是true
在发生Minor之前,会检查老年代最大可用的连续空间是否大于新生代所有对象的总和,历次晋升老年代对象的平均大小,如果大于新生代对象的总和,或者大于历次平均值就进行Minor GC,否则进行Full GC
jps
查询进程
jinfo -flag 标识 进程ID
jinfo -flag NewRatio 9089
查看进程详情
-XX:+DoEscapeAnalysis
显式开启逃逸分析
new的对象实体是否有可能在方法外被调用
逃逸分析(在服务端模式下才可以)
子主题
是一种可以有效减少java程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法;
通过逃逸分析,java编译器能够分析出一个新的对象的引用的使用范围,从而决定是否将此对象分配到堆上;
逃逸分析的基本做法就是分析对象动态作用范围
通过逃逸分析,java编译器能够分析出一个新的对象的引用的使用范围,从而决定是否将此对象分配到堆上;
逃逸分析的基本做法就是分析对象动态作用范围
当一个对象在方法中被定义,对象只在方法内部使用,对象没有发生逃逸
当一个对象在方法中被定义,他被外部方法引用,对象发生了逃逸,例如作为调用参数传递到其他方法中
-XX:+DoEscapeAnalysis
显式开启逃逸分析
-XX:+PrintEscapeAnalysis
查看逃逸分析的筛选结果
new的对象实体是否有可能在方法外被调用,在外部调用表示发生了逃逸分析,否则没有发生
开发中如果能使用局部变量的,就尽量不要使用方法外定义
实例:-server -Xms1G -Xmx1G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails
代码优化的策略(前提条件是逃逸分析)
栈上分配
在JVM中,对象是在java堆中分配内存的,但是有一些特殊情况,就是经过逃逸分析(Escape Analysis)发现,一个对象并没有逃逸出方法的话,那么就有可能被优化成栈上分配,这样就无须在堆上分配内存,也无需进行垃圾回收,这就是常见的堆外存储技术。
将堆分配转换为栈分配,如果一个对象在子程序中被分配,
要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配;
随着方法的执行结束,栈空间被移除,局部变量空间也被回收,这样无需进行垃圾回收
要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配;
随着方法的执行结束,栈空间被移除,局部变量空间也被回收,这样无需进行垃圾回收
-Xms1G -Xmx1G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails 关闭逃逸分析
-Xms1G -Xmx1G +XX:+DoEscapeAnalysis -XX:+PrintGCDetails 开启逃逸分析,栈上分配
同步省略
如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步
线程同步的代价是相当高的,同步的后果是降低程序的并发性和性能
在动态编译同步块的时候,JIT编译器会借助逃逸分析,判断同步块所使用的的锁对象是否只能被一个线程访问,而没有被发布到其他线程,
如果没有,JIT编译器就会取消对这部分代码的同步,这样能够提高并发性和性能
如果没有,JIT编译器就会取消对这部分代码的同步,这样能够提高并发性和性能
分离对象或者标量替换
有的对象可能不需要一个连续的内存结构存在也可以被访问,那么对象的部分(全部)不需要存储在内存,而是存储在cpu寄存器中
标量(Scalar):指一个无法再被分解成更小的数据的数据,java中的原始数据类型就是标量
聚合量(Aggregate):那些还可以分解的数据,java中的对象就是聚合量,因为他们还可以分解成其他标量和聚合量
在JIT阶段,经过逃逸分析,一个对象不会被外界访问的话,经过JIT优化,就会把这个对象拆解成若干个成员变量来代替这个过程就是标量替换.
通过 -XX:+EliminateAllocations 开启变量替换(默认打开),允许将变量打散分配到栈上
实例:-server -Xms100M -Xmx100M -XX:+DoEscapeAnalysis -XX:-EliminateAllocations -XX:+PrintGCDetails
逃逸分析并不成熟
无法保证逃逸分析的性能消耗低于其他的性能消耗,逃逸分析本身也需要一些列复杂的分析,也是一种相对耗时的过程;
但是他是即时编译器优化技术中一种十分重要的手段
但是他是即时编译器优化技术中一种十分重要的手段
子主题
方法区
尽管所有的方法区在逻辑上是堆的一部分,但是一些简单的实现可能不会选择进行垃圾回收或者是压缩;
但是对于hotspot JVM,方法区还有一个别名就是Non-Heap(非堆),目的就是要和堆分开;
方法区看做是一块独立于java堆的内存空间;
但是对于hotspot JVM,方法区还有一个别名就是Non-Heap(非堆),目的就是要和堆分开;
方法区看做是一块独立于java堆的内存空间;
方法区(Method Area)和java堆一样,是各个线程共享的内存区域
方法区在JVM启动的时候被创建,JVM关闭的时候释放这个区域的内存,并且它的实际的物理内存空间和java堆区内存是一样的可以不连续
方法区的大小和堆的大小一样,是可以固定大小,也可以扩展大小;
元空间和永久代之间的本质区别是:元空间不在JVM设置的内存中,而是本地内存中
元空间和永久代之间的本质区别是:元空间不在JVM设置的内存中,而是本地内存中
1.8以前
-XX:PermSize
设置永久代初始分配空间,默认值是20.75M
-XX:MaxPermSize
设置永久代最大可分配空间,32位机器默认是64M,64位机器默认是82M
当超过最大值的时候会报java.lang.OutOfMemoryError:permgen space;
例如:-XX:PermSize=256m -XX:MaxPermSize=256m
1.8及以后
-XX:MetaspaceSize
设置元空间初始分配空间,WIndow(默认值是21M)
-XX:MaxMetaspaceSize
设置元空间最大可分配空间,WIndow(默认值是-1,表示没有限制)
与永久代不同的是,当没有设置MaxMetaspaceSize的时候,JVM会耗尽所有系统可用内存,当超过最大值的时候会报java.lang.OutOfMemoryError:metaSpace
例如:-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m
优化经验
如何解决OOM?
方法区的大小决定了系统中可以保存多少个类,如果系统中定义了过多的类(加载大量的第三方jar,大量动态的生成反射类,tomcat发布过多的项目);
会导致方法区溢出,JVM同样会抛出OOM异常;
1.8之前是java.lang.OutOfMemoryError:permgen space;
1.8及以后是java.lang.OutOfMemoryError:metaSpace;
元空间和永久代之间的本质区别是:元空间不在JVM设置的内存中,而是本地内存中
会导致方法区溢出,JVM同样会抛出OOM异常;
1.8之前是java.lang.OutOfMemoryError:permgen space;
1.8及以后是java.lang.OutOfMemoryError:metaSpace;
元空间和永久代之间的本质区别是:元空间不在JVM设置的内存中,而是本地内存中
方法区存储什么?
1.8以后无永久代,改为元空间,字符串常量池、静态变量存储在堆中,类型信息、域(字段)、方法、运行时常量池保存在本地内存方法区(元空间)
类型信息
对每个加载的类型(class、interface、enum、annotation),JVM必须在方法区中存储以下类型信息:
1、这个类型的完整有效名称(全名=包名.类名);
2、这个类型直接父类的完整有效名(interface、Object都没有直接父类);
3、这个类型的修饰符(public、abstract、final等);
4、这个类型的直接接口的有序列表(可以实现多个接口)
1、这个类型的完整有效名称(全名=包名.类名);
2、这个类型直接父类的完整有效名(interface、Object都没有直接父类);
3、这个类型的修饰符(public、abstract、final等);
4、这个类型的直接接口的有序列表(可以实现多个接口)
域(Field)信息
JVM必须在方法区中保存类型的所有域的相关信息和域声明的顺序;
域的相关信息:域修饰符(private、public、final、transient等)、域类型、域名称、
域的相关信息:域修饰符(private、public、final、transient等)、域类型、域名称、
方法信息
JVM必须在方法区中保存方法的相关信息和声明的顺序;
1、方法名、方法的返回类型、参数的数量和类型、方法的修饰符
2、方法实体里面的字节码、操作数栈、局部变量表及大小
3、异常表(abstract和native方法除外),每个异常的开始位置,结束位置,代码处理中程序计数器中的偏移地址,被捕获的异常类的常量池索引
1、方法名、方法的返回类型、参数的数量和类型、方法的修饰符
2、方法实体里面的字节码、操作数栈、局部变量表及大小
3、异常表(abstract和native方法除外),每个异常的开始位置,结束位置,代码处理中程序计数器中的偏移地址,被捕获的异常类的常量池索引
运行时常量池
常量池
为什么要有常量池
一个java源文件类或者接口在编译以后,会产生一个字节码文件,字节码需要数据支持,通常支持数据会很大,以至于不能存到字节码里,
可以存到常量池,这个生成的字节码包含了指向常量池的引用,在动态链接的时候会用到运行时常量池数据,进行程序运行
可以存到常量池,这个生成的字节码包含了指向常量池的引用,在动态链接的时候会用到运行时常量池数据,进行程序运行
常见的几种在常量池中存储的数据类型
数量值
字符串值
类引用
字段引用
方法引用
class文件的一部分,用于存放编译期的字面量和符号引用,这部分内容在将字节码文件加载到方法区后变成运行时常量池
常量池可以看做是一张表,JVM指令根据这张表找到要执行类名、方法名、参数类型、字面量等类型
字面量指在程序中自己写的值
将字节码常量加载到方法区以后,这些常量就被称之为运行时常量池,
运行时常量池是方法区的一部分,当加载类或者接口到JVM的时候,
就会创建对应的运行时常量池,JVM为每一个已加载的类型(类/接口),
都维护一个常量池,池中的数据像数组一样,通过索引进行访问
运行时常量池是方法区的一部分,当加载类或者接口到JVM的时候,
就会创建对应的运行时常量池,JVM为每一个已加载的类型(类/接口),
都维护一个常量池,池中的数据像数组一样,通过索引进行访问
静态变量
静态变量和类关联在一起,随着类的加载而加载,他们成为类数据在逻辑上的一部分;
1、类变量是被类的所有实例共享,即使没有类实例也可以访问他;
2、被声明为final类变量处理方法则不同,每个全局常量(static final)在编译的时候就会被分配
JIT编译后的代码缓存
1.8以后用元空间替换永久代原因
官网与jrockit合并的原因,jrockit没有永久代
为永久代设置空间大小很难确定,容易导致full GC,同时导致OOM的可能性比较大,
使用本地内存,内存空间只受本地内存限制,更灵活
使用本地内存,内存空间只受本地内存限制,更灵活
对永久代调优很困难,回收被废弃的常量池中的常量很容易,但是校验类型是否被废弃很困难
方法区垃圾回收回收的东西-
JVM规范中说可以对方法区进行垃圾回收,
也可以不回收
JVM规范中说可以对方法区进行垃圾回收,
也可以不回收
回收运行时常量池中废的常量;
只要常量没有被引用就回收
只要常量没有被引用就回收
字面量
文本字符串
声明为final的常量值
符号引用
类和接口的全限定名
字段的名称和描述符
方法的名称和描述符
不在使用的类型
三个条件是且的关系
三个条件是且的关系
该类的所有实例都已经被回收,也就是java堆中不存在该类的实例和派生的子类的实例
该类的类加载器已经被回收
该类对应的Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法
在大量使用反射,动态代理等框架中,频繁自定义类加载器的场景中,需要jVM需要类型卸载的能力,以免对方法区的内存造成过大压力
对象的存放
对象的引用
1.6及以前-类的静态变量随着类型(类或者接口)存放在方法区)
1.7及以后在hotspot jvm静态变量和类型在java语言一端的映射Class对象存放在一起,存储在java堆之中
1.7及以后在hotspot jvm静态变量和类型在java语言一端的映射Class对象存放在一起,存储在java堆之中
类的成员变量随着类型(类或者接口)的对象实例存放在java堆
方法的局部变量存放在方法的栈帧的局部变量表中
上述三个对象的数据在内存中的地址都是在Eden区中,只要是对象很实例必然在java堆中分配
javap -v xxxx.class 反编译class文件
本地方法接口
一个Native Method就是一个java调用非java代码的接口,一个Native Method是这样一个方法,该方法的实现由非java语言实现
由native标识的方法,native可以与所有其他java标识符连接使用,abstract除外
面试题
说一下JVM内存模型,有哪些区,分别是干什么的?
jdk8内存分代改进?
JVM内存分哪几个区,每个区的作用是什么?
JVM内存分布/内存结构?
堆和栈的区别?
堆的结构?
新生代中为什么要有Eden和Survivor区,他们的比例是多少?
为什么要分两个Survivor区?
为什么要分两个Survivor区?
如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。
Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)
Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)
JVM内存分区,为什么要有新生代、老年代、持久代?
讲讲JVM运行时数据区?
什么时候对象会进入老年代?
JVM永久代会发生垃圾回收吗?
GC Roots
内存中已经不在被使用到的空间就是垃圾
垃圾回收的算法
引用计数法
复制算法
标记清除
标记整理
如果判断一个对象是否是垃圾,可以被回收,
GC Roots:一组必须活跃的引用
GC Roots:一组必须活跃的引用
java使用了可达性分析算法:
就是通过一系列名为“GC Roots”的对象作为起点,从这个名为GC Roots的对象开始搜索,
如果一个对象到GC Roots没有任何引用链相连时,则说明对象不可用;
就是通过一系列名为“GC Roots”的对象作为起点,从这个名为GC Roots的对象开始搜索,
如果一个对象到GC Roots没有任何引用链相连时,则说明对象不可用;
哪些对象可以作为GC Roots的对象
1、虚拟机栈中引用的对象(栈帧中的局部变量,也叫作局部变量表)
2、方法区中的类静态属性引用的对象
3、方法区中常量引用的对象
4、本地方法区中JNI(Native)引用的对象
盘点查看JVM系统默认值
JVM参数类型
Boolean类型
-XX:+或者-某个属性值(+表示开启,-表示关闭)
KV设置类型
-XX:属性key=属性value
jinfo -flag MetaspaceSize 进程号
*jinfo -flag MaxTenuringThreshold 7316
*-XX:MaxTenuringThreshold=15
新生代转老年代的复制次数
*-XX:MaxTenuringThreshold=15
新生代转老年代的复制次数
jinfo -flag InitialHeapSize 7316 //初始化的堆大小
jinfo -flags 7316 //查询所有的JVM配置信息
-Xms100m 等价于 -XX:InitialHeapSize=100m
-Xmx500m 等价于 -XX:MaxHeapSize=100m
-Xmx500m 等价于 -XX:MaxHeapSize=100m
根据系统的不同Xms默认是系统内存的1/64,Xmx默认是系统内存的1/4
java -XX:+PrintFlagsInitial -version //查看JVM出厂默认设置
java -XX:+PrintFlagsInitial //查看JVM出厂默认设置,结果中(=表示没有修改过,:=表示修改更新过)
java -XX:+PrintFlagsFinal -version //查看JVM修改更新以后的设置,结果中(=表示没有修改过,:=表示修改更新过)
java -XX:+PrintCommandLineFlags -version //查询JVM系统配置参数,同时查看当前JVM使用什么垃圾回收器
info举例,如何查看当前运行程序的配置
jps -l //查询线程号
jinfo -flag PrintGCDetails 线程号 //查询是否开启
内存调优参数
-Xms100m 等价于 -XX:InitialHeapSize=100m //设置堆的初始大小
-Xmx500m 等价于 -XX:MaxHeapSize=100m //设置堆的最大大小
-Xmx500m 等价于 -XX:MaxHeapSize=100m //设置堆的最大大小
根据系统的不同Xms默认是系统内存的1/64,Xmz默认是系统内存的1/4
-Xss512K 等价于 -XX:TreadStackSize=512k //设置单个线程栈的大小 默认是512K-1024K
jinfo -flag ThreadStackSize 7896
-Xmn100m //设置年轻代大小
年轻代占堆内存的1/3,老年代占堆内存的2/3.
eden和survivor官网比例是8:1:1,实际测试是6:1:1
eden和survivor官网比例是8:1:1,实际测试是6:1:1
-XX:MetaspaceSize=209715200 //设置元空间大小
jinfo -flag MetaspaceSize 7896
元空间不在虚拟机,而是使用本地内存,元空间的大小受本地空间限制,永久代使用的是JVM内存,
但是如果不进行调整,JVM默认只有20M左右,因此需要调整一下元空间
但是如果不进行调整,JVM默认只有20M左右,因此需要调整一下元空间
-Xms100m -Xmx500m -Xss1024k -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC
-XX:+PrintGCDetails //GC收集日志信息
Young GC分析
Full GC分析
-XX:SurvivorRatio //幸存区比例
-Xms100m -Xmx100m -XX:+PrintGCDetails -XX:SurvivorRatio=4
-XX:NewRatio
-Xms100m -Xmx100m -XX:+PrintGCDetails -XX:SurvivorRatio=4 -XX:NewRatio=4
-XX:MaxTenuringThreshold
MaxTenuringThreshold of 20 is invalid; must be between 0 and 15
引用
强引用
软引用
软引用和弱引用使用场景
WeakHashMap //进行GC的时候会被回收,方便做高速缓存
弱引用
软引用和弱引用使用场景
WeakHashMap //进行GC的时候会被回收,方便做高速缓存
虚引用
必须和引用队列ReferenceQueue一起使用
JVM错误
java.lang.StackOverflowError
java栈异常因为栈空间只有512k-1024k,满了就会抛出异常
java.lang.OutOfMemoryError: Java heap space
jvm堆满了报出异常
java.lang.OutOfMemoryError: GC overhead limit exceeded
GC回收时间过长就会抛出次错误,超过98%的时间用来进行垃圾回收,并且回收不到2%的堆内存;
GC每次清理出来的内存,很快就会被填满,触发下一次GC,导致了恶性循环,CPU使用率很高,但是GC没有效果
GC每次清理出来的内存,很快就会被填满,触发下一次GC,导致了恶性循环,CPU使用率很高,但是GC没有效果
java.lang.OutOfMemoryError: Direct buffer memory
-XX:MaxDirectMemorySize=5m //设置直接内存,分配在本地内存中
直接内存无法由jvm回收
-XX:MetaspaceSize=209715200 //设置元空间大小,分配在本地内存中
元空间虽然是在jvm外开辟的高速内存区,但是还是由jvm来监控与gc
java.lang.OutOfMemoryError: unable to create new native thread
* 高并发的请求服务,有可能会报:java.lang.OutOfMemoryError: unable to create new native thread
* 1、native thread线程数和对应的平台有关
* 2、你的服务器并不允许,一个应用进程创建太多线程,超过了系统的承载能力,linux系统默认一个进程最多可以创建的线程数1024个,阿里云centos7不 同用户默认是4096
* 查询一个进程允许最大的线程数:cat /proc/sys/kernel/threads-max
* 3、降低一个应用创建的线程数,如果需要扩展需要更改linux的配置
* 1、native thread线程数和对应的平台有关
* 2、你的服务器并不允许,一个应用进程创建太多线程,超过了系统的承载能力,linux系统默认一个进程最多可以创建的线程数1024个,阿里云centos7不 同用户默认是4096
* 查询一个进程允许最大的线程数:cat /proc/sys/kernel/threads-max
* 3、降低一个应用创建的线程数,如果需要扩展需要更改linux的配置
java.lang.OutOfMemoryError: Metaspace
-XX:MetaspaceSize=5m -XX:MaxMetaspaceSize=51m
系统在什么情况下会出现内存溢出?
堆内存溢出:创建过多或者过大的字符串常量,创建过多的对象,例如引用过多的第三方包
栈内存溢出:栈中存放的是局部变量表,方法,方法返回地址,动态链接,当无限循环调用一方法的时候会出现内存溢出
元空间内存溢出:元空间存储的是类信息,属性信息,运行时常量,如果类过多会报错
栈内存溢出:栈中存放的是局部变量表,方法,方法返回地址,动态链接,当无限循环调用一方法的时候会出现内存溢出
元空间内存溢出:元空间存储的是类信息,属性信息,运行时常量,如果类过多会报错
垃圾收集器
垃圾回收算法是方法论,垃圾收集器是算法的实现
垃圾回收的算法(不同的代用不同的算法,分带收集)
引用计数法
复制算法
用在新生代
标记清除
老年代
标记整理
老年代
垃圾收集器
Serial(串行)
他为单线程环境设计,且只使用一个线程进行垃圾回收,会暂停所有的用户线程(STW),不适合服务器环境
Parallel(并行)
多个垃圾收集器并行工作,此时会暂停所有的用户线程(STW),适用于科学计算/大数据处理等弱环境场景
CMS(ConcMarkSweep并发标记清除,会产生碎片)
用户线程和垃圾收集线程同时执行(不一定是并行,可能是交替执行),不需要停顿用户线程;
互联网公司大多数用这个,适用对响应时间有要求的场景
互联网公司大多数用这个,适用对响应时间有要求的场景
G1
将堆内存分割成不同的区域,然后并发的进行垃圾回收
查询垃圾收集器命令
查看默认的垃圾收集器
java -XX:+PrintCommandLineFlags -version
//查询结果中有一个-XX:+UseParallelGC
//查询结果中有一个-XX:+UseParallelGC
默认的垃圾收集器有哪些?使用范围
-XX:+UseSerialGC
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseSerialGC
-XX:+UseSerialOldGC //弃用了
-XX:+UseParNewGC
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParNewGC
-XX:+UseParallelGC
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParallelGC
-XX:+UseParallelOldGC
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParallelOldGC
-XX:+UseConcMarkSweepGC
4个阶段
1、初始标记:只是标记一下GC Roots能直接关联的对象,速度很快,但是仍然需要暂停所有的线程
2、并发标记和用户线程一起:进行GC跟踪的过程和用户线程一起,不需要暂停工作线程,主要标记过程,标记全部对象
3、重新标记:由于并发标记时候,用户线程依然执行,因此在正式清理前,再次进行确认修正,需要暂停所有的工作线程
4、并发清除和用户线程在一起:清理GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程,基于标记结果,直接清理对象
优点是:并发收集低停顿;
缺点:并发执行对CPU资源压力大,采用标记清除算法会产生碎片
缺点:并发执行对CPU资源压力大,采用标记清除算法会产生碎片
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC
-XX:+UseG1GC
G1以前的垃圾收集器的特点
1、年轻代和老年代是各自独立且连续的内存块
2、年轻代收集使用单Eden+s0+s1进行复制算法
3、老年代收集必须扫描所有老年代区域
4、都以尽可能少和快速的进行GC为设计原则
G1是什么?
特点
1、和CMS收集器一样,可以和用户线程并发执行,这里空闲空间更快,需要更多的时间来预测GC停顿时间,
不希望牺牲大量的吞吐量,不需要更多的java Heap,采用标记整理算法,没有内存碎片,同时用户可以更改期望停顿时间;
避免了全内存扫描,只需要按照区域进行扫描即可
不希望牺牲大量的吞吐量,不需要更多的java Heap,采用标记整理算法,没有内存碎片,同时用户可以更改期望停顿时间;
避免了全内存扫描,只需要按照区域进行扫描即可
2、主要改变是Eden、S0/1、Old内存区域,不再是连续的了,而是变成了一个大小不一的region,每个region的大小是1-32M不等,一个region可能属于Eden、S0/1、Old内存区域,G1并不要求内存时物理上连续的只要逻辑上连续即可,每个分区也不会固定的为某个代服务,可以按需在年轻代和老年代上切换,启动时可以通过-XX:G1HeapRegionSize=n可以指定分区大小(1-32M,必须是2的幂),默认将整堆划分为2048个分区,
即能够支持的最大内存为:2048*32M=64G
即能够支持的最大内存为:2048*32M=64G
收集过程
常见参数
-XX:+UseG1GC //设置垃圾收集器为G1
-XX:G1HeapRegionSize=n //可以指定分区大小(1-32M,必须是2的幂),块数默认是2048个
-XX:MaxGCPauseMillis //最大停顿时间,JVM尽量停顿时间小于这个时间
-XX:InitiatingHeapOccupancyPercent //堆占用百分比是多少时候触发GC,默认45%
-XX:ConcGCThreads //并发GC使用的线程数,默认是-XX:ParallelGCThreads/4
-XX:G1ReservePercent //G1为分配担保预留的空间比例:通过-XX:G1ReservePercent指定,默认10%
和CMS的对比
G1不会产生内存碎片,CMS是全内存扫描
可以精确控制停顿时间,G1收集器是把堆内存(新生代,老年代)划分为多个大小相同的区域,每次根据最多停顿的时间去回收垃圾最多的区域
如何选择垃圾收集器?
java -server JVM参数配置 -jar xxxx.jar/war
linux命令
整机
top
load average: 0.05, 0.12, 0.18 //表示系统复杂均衡 1/5/15各种系统平均负载值,三个数相加/3*100>=60表示系统负载过重
查询cpu和内存使用情况
按1可以看到cpu数量
uptime
查询系统负载均衡情况
1、查询当前整个系统已用的线程或进程数
pstree -p | wc -l
pstree -p | wc -l
pstree -p 进程号 | wc -l
系统限制某用户下最多可以运行多少进程或线程,使用命令:ulimit -u
https://www.cnblogs.com/nizuimeiabc1/p/5593637.html
cpu
vmstat
vmstat -n 2 3 //每2秒采样一次,采样3次
mpstat -P ALL 2 3 //查看所有cpu核信息,每2秒钟采用一次,总共采样3次
pidstat -u 1 -p 进程编号 //查询某个进程CPU占用情况,每1秒钟采样一次
内存
free -m //按照MB查询内存情况
pidstat -p 进程号 -r 2 //每2秒钟查询进程使用内存情况
磁盘
df -h
磁盘IO
iostat -xdk 2 3 //每2秒钟采用3次
pidstat -d 采样间隔秒数 -p 进程号
网络IO
ifstat 1 //每1秒采样一次
CPU占用过高的问题
1、top定位哪个进程占用内存过高
2、进一步定位到底是哪个进程有问题
jps -l
ps -ef|grep java|grep -v grep
3、定位到具体线程或者是代码
ps -mp 915 -o THREAD,tid,time 或者top -Hp pid: 即 top -Hp 915
//915是进程号,找到对应的线程号
//-m显示所有的线程,p表示进程使用CPU的时间
//-o 后面表示用户自定义的格式
//915是进程号,找到对应的线程号
//-m显示所有的线程,p表示进程使用CPU的时间
//-o 后面表示用户自定义的格式
4、将3查询出的线程Id转换成16进制格式,方式为: printf "%x\n" 915 //915为线程ID
5、定位代码在哪一行
通过命令: jstack 进程ID | grep 线程ID(16进制小写的ID) -A60
通过命令: jstack 进程ID | grep 线程ID(16进制小写的ID) -A60
jstack 915|grep 393 -A60 //-A60表示查询钱60行
对于jdk自带的jvm监控和分析工具有哪些,怎么用
jstat
jstat -gc 进程ID : 查询进程ID使用的内存情况(进程ID通过jps命令查询到的)
S0C:第一个幸存区的大小
S1C:第二个幸存区的大小
S0U:第一个幸存区的使用大小
S1U:第二个幸存区的使用大小
EC:伊甸园区的大小
EU:伊甸园区的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法区大小(元空间)
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
S1C:第二个幸存区的大小
S0U:第一个幸存区的使用大小
S1U:第二个幸存区的使用大小
EC:伊甸园区的大小
EU:伊甸园区的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法区大小(元空间)
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
子主题
jstat -gccapacity 3676(进程ID)
堆内存统计
jstat -gcnew 3676
新生代垃圾回收统计
jstat -gcnewcapacity 3676
新生代内存统计
jstat -gcold 3676
老年代垃圾回收统计
jmap 查看内存信息
jmap -histo 3676 > histo.txt
num:序号
instances:实例数量
bytes:占用空间大小
class name:类名称
jmap -heap 3676
查看堆信息
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=jvm.dump(文件路径)
内存溢出自动导出dump文件
jmap -dump:format=b,file=dumpFileName pid
然后使用jhat来对上面dump出来的内容进行分析(jhat -J-Xmx512m -port 8888 /home/dump.dat )
jstack //某个Java进程内的线程堆栈信息
查询死锁
jps //查询进程
jstack 2324 //进程ID,查询信息
JvisualVM自动可以检测死锁
0 条评论
下一页