开源Java诊断工具,采用命令行交互模式,可方便的定位和诊断线上程序运行问题。
Arthas使用
wget https://alibaba.github.io/arthas/arthas‐boot.jar wget https://arthas.gitee.io/arthas‐boot.jar # 下载结束后,使用java -jar启动即可
测试代码
package io;
import java.util.HashSet;
public class Arthas {
private static HashSet hashSet = new HashSet();
public static void main(String[] args) {
cpuHigh();
deadThread();
addHashSetThread();
}
public static void addHashSetThread() {
new Thread(() -> {
int count = 0;
while (true) {
try {
hashSet.add("count" + count);
Thread.sleep(1000);
count++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
public static void cpuHigh() {
new Thread(() -> {
while (true) {
}
}).start();
}
public static void deadThread() {
Object resourceA = new Object();
Object resourceB = new Object();
Thread threadA = new Thread(() -> {
synchronized (resourceA) {
System.out.println(Thread.currentThread() + " get ResourcesA");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + " waiting get resourceB");
synchronized (resourceB) {
System.out.println(Thread.currentThread() + " get resourceB");
}
}
});
Thread threadB = new Thread(() -> {
synchronized (resourceB) {
System.out.println(Thread.currentThread() + " get ResourceB");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resourceA");
synchronized (resourceA) {
System.out.println(Thread.currentThread() + " get resourceA");
}
}
});
threadA.start();
threadB.start();
}
}
dashboard
查看整个进程的运行情况,线程,内存,GC,运行环境信息
thread
查看线程详细情况
thread
查看线程堆栈信息
thread -b
查看线程死锁
jad <类的全名,包.类名>
反编译代码
ognl <线上系统变量的值>
可以查看线上系统变量的值,甚至可以修改变量的值。
GC日志详解在JVM启动参数中添加参数,把程序运行过程中所有GC日志打印出来,分析GC日志得到关键性指标,分析GC原因。
‐Xloggc:./gc‐%t.log ‐XX:+PrintGCDetails ‐XX:+PrintGCDateStamps ‐XX:+PrintGCTimeStamps ‐XX:+PrintGCCause ‐XX:+UseGCLogFileRotation ‐XX:NumberOfGCLogFiles=10 ‐XX:GCLogFileSize=100M
gceasy工具协助可视化GC情况。
Class常量池与运行时常量池
Class常量池可以理解为Class文件中的资源仓库。Class文件中包含类的版本、字段、方法、接口、常量池等信息,用于存放编译器生成的字面量和符号引用。
一个Class文件的16进制大体结构
16进制编码对应含义
javap -v xx.class
Constant pool就是class常量池信息,主要存放字面量和符号引用。
字面量
由字母、数字等构成的字符串或者数值常量,等号右边的值。
符号引用
主要包括:类和接口的全限定名;字段的名称和描述符;方法的名称和描述符
常量池中现在是静态信息,只有到运行时被加载到内存后,这些符号才有对应的内存地址信息,被装入内存后就成为运行时常量池,对应的符号引用在程序加载或运行时会转变为被加载到内存区域的代码的直接引用,也就是动态链接,存储在对象头中的类型指针去转换直接引用。
字符串常量池
字符串常量池的设计思想
1. 字符串的分配和其他的对象分配一样,耗费高昂的时间与空间代价,频繁的创建字符串会影响程序的性能;
2. JVM进行的优化
为字符串开辟一个字符串常量池,类似于缓存区;
创建字符串常量时,首先查询常量池中是否存在该字符;
存在该字符,返回引用实例,不存在,实例化该字符串并放入池中
三种字符串操作
String s = "abc";
这种方式创建的字符串对象,只会在常量池中,在创建对象s的时候,JVM会先在常量池中查找,如果没有再创建一个新对象。
String s = new String("abc");
这种方式会保证字符串常量池和堆中都有这个对象,没有就创建,最后返回堆内存中的对象引用。
在字符串常量池中寻找,没有会新创建,然后再去堆内存中创建一个字符串对象。
String 2 = s.intern()
如果池已经包含一个等于此String对象的字符串 (用equals(oject)方法确定),则返回池中的字符串。否则,将intern返回的引用指向当前字符串 s1
jdk1.8以后,运行池常量区存储在元空间,字符串常量池存储在堆。
字符串常量池底层类似一个HashTable(C++实现)存储着字符串对象的引用。
《详细图解》
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
String s3 = "hel" + "lo";
System.out.println(s1==s2);
System.out.println(s1==s3);
}
"hello", "hel", "lo"都是存储在字符串常量池中的字符串常量,在常量池中先找,如果不存在才会创建,所以s1=s2=s3
JVM对于字符串常量的"+"号连接,将在程序编译期,JVM就将常量字符串的"+"连接优化为连接后的值,拿"hel" + "lo"来说,经编译器优化后在class中就已经是hello 。在编译期其字符串常量的值就确定下来,故上面程序最终的结果都为 true。
public static void main(String[] args) {
String s1 = "hello";
String s2 = new String("hello");
String s3 = "hel" + new String("lo");
System.out.println(s1==s2);
System.out.println(s1==s3);
System.out.println(s2==s3);
}
new String()创建的并不是常量,不会存储到常量池中,他们有自己的内存空间。所以结果均不等。
String是不可变的
八种基本数据类型的包装类和对象池
java中基本类型的包装类的大部分都实现了常量池技术(严格来说应该叫对象池,在堆上),这些类是 Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外 Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用对象池,也即对象不负 责创建和管理大于127的这些类的对象。因为一般这种比较小的数用到的概率相对较大。



