1.查看JVM配置及其应用使用情况
1.1在启动应用中进行测试
1jsp命令的使用
- 1.jsp查看当前启动的程序
- 2.查看相关堆中对象的使用情况
jmap -histo 16700>./log.txt
- num:序号
- instances:实例数量
- bytes:占用空间大小
- class name:类名称,[C is a char[],[S is a short[],[I is a int[],[B is a byte[],- [[I is a int[][]
2堆信息
1.查看命令
jmap -heap 16700
2.堆内存信息
- 1 .导出命令
jmap -dump:format=b,file=eureka.hprof 16700
- 2 .可以用jvisualvm命令工具导入该dump文件分析
3.jstack查看锁住的进程
1.查看相关的锁
*程序
package com.jvm;
public class DeadLockTest {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock1) {
try {
System.out.println("thread1 begin");
Thread.sleep(5000);
} catch (InterruptedException e) {
}
synchronized (lock2) {
System.out.println("thread1 end");
}
}
}).start();
new Thread(() -> {
synchronized (lock2) {
try {
System.out.println("thread2 begin");
Thread.sleep(5000);
} catch (InterruptedException e) {
}
synchronized (lock1) {
System.out.println("thread2 end");
}
}
}).start();
System.out.println("main thread end");
}
}
查询相关的进程
jps
*查询相关的进程id
通过jvisualvm查看相关的进程
2.在linux下查看cpu最高堆栈信息
- 1,使用命令top -p ,显示你的java进程的内存情况,pid是你的java进程号,比如19663
- 2,按H,获取每个线程的内存情况
- 3,找到内存和cpu占用最高的线程tid,比如19664
- 4,转为十六进制得到 0x4cd0,此为线程id的十六进制表示
- 5,执行 jstack 19663|grep -A 10 4cd0,得到线程堆栈信息中 4cd0 这个线程所在行的后面10行,从堆栈中可以发现导致cpu飙高的调用方法
- 6,查看对应的堆栈信息找出可能存在问题的代码
4.Jinfo
4.1.查看正在运行的Java应用程序的扩展参数
jsp
jinfo -flags 25984
4.2查看java系统参数
jps
jinfo -sysprops 25984
5.Jstat
5.1垃圾回收统计
jps
jstat -gc 25984
- S0C:第一个幸存区的大小,单位KB
- S1C:第二个幸存区的大小
- S0U:第一个幸存区的使用大小
- S1U:第二个幸存区的使用大小
- EC:伊甸园区的大小
- EU:伊甸园区的使用大小
- OC:老年代大小
- OU:老年代使用大小
- MC:方法区大小(元空间)
- MU:方法区使用大小
- CCSC:压缩类空间大小
- CCSU:压缩类空间使用大小
- YGC:年轻代垃圾回收次数
- YGCT:年轻代垃圾回收消耗时间,单位s
- FGC:老年代垃圾回收次数
- FGCT:老年代垃圾回收消耗时间,单位s
- GCT:垃圾回收消耗总时间,单位s
5.2堆内GC统计
jps
jstat -gccapacity 25984
- NGCMN:新生代最小容量
- NGCMX:新生代最大容量
- NGC:当前新生代容量
- S0C:第一个幸存区大小
- S1C:第二个幸存区的大小
- EC:伊甸园区的大小
- OGCMN:老年代最小容量
- OGCMX:老年代最大容量
- OGC:当前老年代大小
- OC:当前老年代大小
- MCMN:最小元数据容量
- MCMX:最大元数据容量
- MC:当前元数据空间大小
- CCSMN:最小压缩类空间大小
- CCSMX:最大压缩类空间大小
- CCSC:当前压缩类空间大小
- YGC:年轻代gc次数
- FGC:老年代GC次数
5.3新生代垃圾回收统计
jps
jstat -gcnew 10832
- S0C:第一个幸存区的大小
- S1C:第二个幸存区的大小
- S0U:第一个幸存区的使用大小
- S1U:第二个幸存区的使用大小
- TT:对象在新生代存活的次数
- MTT:对象在新生代存活的最大次数
- DSS:期望的幸存区大小
- EC:伊甸园区的大小
- EU:伊甸园区的使用大小
- YGC:年轻代垃圾回收次数
- YGCT:年轻代垃圾回收消耗时间
5.4新生代内存统计
jps
jstat -gcnewcapacity 10832
- NGCMN:新生代最小容量
- NGCMX:新生代最大容量
- NGC:当前新生代容量
- S0CMX:最大幸存1区大小
- S0C:当前幸存1区大小
- S1CMX:最大幸存2区大小
- S1C:当前幸存2区大小
- ECMX:最大伊甸园区大小
- EC:当前伊甸园区大小
- YGC:年轻代垃圾回收次数
- FGC:老年代回收次数
5.5老年代垃圾回收统计
jps
jstat -gcold 10832
- MC:方法区大小
- MU:方法区使用大小
- CCSC:压缩类空间大小
- CCSU:压缩类空间使用大小
- OC:老年代大小
- OU:老年代使用大小
- YGC:年轻代垃圾回收次数
- FGC:老年代垃圾回收次数
- FGCT:老年代垃圾回收消耗时间
- GCT:垃圾回收消耗总时间
5.6老年代内存统计
jps
jstat -gcoldcapacity 10832
- OGCMN:老年代最小容量
- OGCMX:老年代最大容量
- OGC:当前老年代大小
- OC:老年代大小
- YGC:年轻代垃圾回收次数
- FGC:老年代垃圾回收次数
- FGCT:老年代垃圾回收消耗时间
- GCT:垃圾回收消耗总时间
5.7元数据空间统计
jps
jstat -gcmetacapacity 10832
- MCMN:最小元数据容量
-MCMX:最大元数据容量
-MC:当前元数据空间大小
-CCSMN:最小压缩类空间大小
-CCSMX:最大压缩类空间大小
-CCSC:当前压缩类空间大小
-YGC:年轻代垃圾回收次数
-FGC:老年代垃圾回收次数
-FGCT:老年代垃圾回收消耗时间
-GCT:垃圾回收消耗总时间
5.8各个空间使用比例
jps
jstat -gcutil 10832
- S0:幸存1区当前使用比例
- S1:幸存2区当前使用比例
- E:伊甸园区使用比例
- O:老年代使用比例
- M:元数据区使用比例
- CCS:压缩使用比例
- YGC:年轻代垃圾回收次数
- FGC:老年代垃圾回收次数
- FGCT:老年代垃圾回收消耗时间
- GCT:垃圾回收消耗总时间
6.通过案例分析配置
6.1记录full gc和minor gc日志,可以在日志中记录相关发生full gc和minor gc次数
jstat -gc 10832 2000 10000 >gc.log
通过这个文件可以分析出,七天内发生发生full gc次数和样GC次数
6.2 分析当前堆内存的大小
jmap -heap 10832
我们可以推测下full gc比minor gc还多的原因有哪些?
1、元空间不够导致的多余full gc
2、显示调用System.gc()造成多余的full gc,这种一般线上尽量通过-XX:+DisableExplicitGC参数禁用,如果加上了这个JVM启动参数,那么代码中调用System.gc()没有任何效果
3、老年代空间分配担保机制
6.3 分析具体代码问题
jmap -histo 10832
查询具体哪个类有问题,然后对类进行修改
7.Arthas使用
7.1下载arthas(Linux)
wget https://arthas.aliyun.com/arthas-boot.jar
7.2启动jar
java -jar arthas-boot.jar
help查询相关的命令
dashboard整个进程的运行情况,线程、内存、GC、运行环境信息
thread 查询当前类运行的线程,thread id查询具体的线程信息
thread -b 查询死锁的线程
jad com.jvm.Arthas 对类进行反编译
ognl @com.jvm.Arthas@hashSet 查看变量的值
** 查看变量的值**
ognl ‘@com.jvm.Arthas@hashSet.add("123")’
8.GC日志记录的保存
8.1 CMS日志
-Xloggc:d:/gc-cms-%t.log -Xms50M -Xmx50M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+PrintGCDetails -XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps -XX:+PrintGCCause -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC
8.2 G1日志
-Xloggc:d:/gc-g1-%t.log -Xms50M -Xmx50M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+PrintGCDetails -XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps -XX:+PrintGCCause -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -XX:+UseG1GC
8.3 GC日志分析工具
https://gceasy.io
可以通过以上连接可以简单分析GC日志的相关内容
8.4JVM参数汇总查看命令
java -XX:+PrintFlagsInitial 表示打印出所有参数选项的默认值
java -XX:+PrintFlagsFinal 表示打印出所有参数选项在运行程序时生效的值
Class常量池与运行时常量池
Class常量池可以理解为是Class文件中的资源仓库。 Class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic References)。
8.5字面量
字面量就是指由字母、数字等构成的字符串或者数值常量
int a = 1;
int b = 2;
int c = "abcdefg";
int d = "abcdefg";
说明:以上的1,2,abcdefg都属于字面量
8.6符号引用
上面的a,b就是字段名称,就是一种符号引用,还有Math类常量池里的 Lcom/tuling/jvm/Math 是类的全限定名,main和compute是方法名称,()是一种UTF8格式的描述符,这些都是符号引用。
这些常量池现在是静态信息,只有到运行时被加载到内存后,这些符号才有对应的内存地址信息,这些常量池一旦被装入内存就变成运行时常量池,对应的符号引用在程序加载或运行时会被转变为被加载到内存区域的代码的直接引用,也就是我们说的动态链接了。例如,compute()这个符号引用在运行时就会被转变为compute()方法具体代码在内存中的地址,主要通过对象头里的类型指针去转换直接引用。
8.7字符串常量池
1.字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能
2.JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
为字符串开辟一个字符串常量池,类似于缓存区
创建字符串常量时,首先查询字符串常量池是否存在该字符串
存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中
1、在 JDK 1.6 中,调用 intern() 首先会在字符串池中寻找 equal() 相等的字符串,假如字符串存在就返回该字符串在字符串池中的引用;假如字符串不存在,虚拟机会重新在永久代上创建一个实例,将 StringTable 的一个表项指向这个新创建的实例。
2、在 JDK 1.7 (及以上版本)中,由于字符串池不在永久代了,intern() 做了一些修改,更方便地利用堆中的对象。字符串存在时和 JDK 1.6一样,但是字符串不存在时不再需要重新创建实例,可以直接指向堆上的实例。
8.7.1字符串常量池的比对
- 字符串比较
String s1="Hello";
String s2="Hello";
String s3="He"+"llo";
System.out.println(s1==s2);
System.out.println(s1==s3);
分析:在编译期,当s1创建的时候,java程序将会在堆里面查看是否有’Hello’这个字符串,没有将会在堆内存的字符串常量池中创建’Hello’字符串,当s2创建时,会直接查看堆字符串常量池中是否有,此时已经存在,直接引用堆里面地址,s3创建时,内存把字符串直接优化为’Hello’,所有三个值都相等
- new字符串比较
String s0="zhuge";
String s1=new String("zhuge");
String s2="zhu" + new String("ge");
String s3=new String("zhuge");
System.out.println( s0==s1 );
System.out.println( s0==s2 );
System.out.println( s1==s2 );
System.out.println( s1==s3 );
分析:在编译期,s0指向的是堆里面的字符串常量池,s1new字符串会在堆里面非字符串常量池开辟一个对象空间,s2会在字符串常量池中创建一个zhu的常量,同时在会在堆里面非字符串常量池开辟一个对象空间,s31new字符串会在堆里面非字符串常量池开辟一个对象空间,所以结果都为false
3. 字符串和数字拼接字符比较
String s1="a"+129;
String s2="a129";
System.out.println(s1==s2);
分析:在编译期,当s1创建的时候,java程序将会在堆里面查看是否有’a129’这个字符串,没有将会在堆内存的字符串常量池中创建’a129’字符串,当s2创建时,会直接查看堆字符串常量池中是否有,此时已经存在,直接引用堆里面字符串常量池地址
- 字符串和数字拼接字符比较
String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println(a == b);
分析:在编译器a会在字符串常量池中生成’ab’,bb会在池中生成’b’,但是b无法在编译器生成,会在运行器生成,动态生成地址给b,所以为false
- 被final修饰的字符比较
String a = "ab";
final String bb = "b";
String b = "a" + bb;
System.out.println(a == b);
分析:和示例4中唯一不同的是bb字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + bb和"a" + "b"效果是一样的。故上面程序的结果为true。
- 被final修饰字符串来至于方法
public static void main(String[] args) {
String a = "ab";
final String bb = getBB();
String b = "a" + bb;
System.out.println(a == b); // false
}
private static String getBB()
{
return "b";
}
分析:bb的引用来至于方法,所以在编译期无法确定值,在运行期会把’a’和方法返回动态分配给b,所以此时为false
- String类型+在内存中计算过程
String s = "a" + "b" + "c";
String a = "a";
String b = "b";
String c = "c";
String s1 = a + b + c;
StringBuilder temp = new StringBuilder();
temp.append(a).append(b).append©;
String s = temp.toString();
- new StringBuilder具体案例
String str2 = new StringBuilder("计算机").append("技术").toString();
System.out.println(str2 == str2.intern());
String str1 = new StringBuilder("ja").append("va").toString();
System.out.println(str1 == str1.intern());
分析:"计算机技术"在字符串常量没有,所以str2 和 str2.intern()返回的是堆里面非字符串常量对象,所以为true,str1 返回的是堆中非字符串常量池中的引用,而str1.intern(),java关键字在创建的时候,就已经创建了,返回的是字符串常量池中的对象。所以为false
- 八种基本类型的包装类和对象池
System.out.println("------Integer------");
Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1 == i2);
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4);
System.out.println("------Double------");
Double i5 = 127d;
Double i6 = 127d;
System.out.println(i5 == i6);
Double i7 = 128d;
Double i8 = 128d;
System.out.println(i7 == i8);
System.out.println("------Short------");
Short s1 = 127;
Short s2 = 127;
System.out.println(s1 == s2);
Short s3 = 128;
Short s4 = 128;
System.out.println(s3 == s4);
System.out.println("------Long------");
Long l1 = 127L;
Long l2 = 127L;
System.out.println(l1 == l2);
Long l3 = 128L;
Long l4 = 128L;
System.out.println(l3 == l4);
System.out.println("------Float------");
Float f1 = 127f;
Float f2 = 127f;
System.out.println(f1 == f2);
Float f3 = 128f;
Float f4 = 128f;
System.out.println(f3 == f4);
System.out.println("------Boolean------");
Boolean i9=true;
Boolean i10=true;
System.out.println(i9 == i10);
分析:在对象池中编译时会初始化一批对象,对象范围在Integer(-128-127之间),Short(-32768~32767),Long(-128-127之间),Fload,Double无初始化值