Arthas和常量池

这篇具有很好参考价值的文章主要介绍了Arthas和常量池。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、Arthas

快速入门 | arthas

1、Arthas使用

运行arthas提供的应用程序

curl -O https://arthas.aliyun.com/math-game.jar
java -jar math-game.jar

 运行arthas工具jar包

curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

 启动界面如下图:

Arthas和常量池,jvm,java,开发语言

 输入1按enter键进入

Arthas和常量池,jvm,java,开发语言

 如果不清楚arthas的命令可以此时输入help查看

查看当前进程信息输入dashboard按回车

Arthas和常量池,jvm,java,开发语言

如果线程占用cpu比较多可以输入thread ID查看栈方法信息,如果需要通过线程匹配具体信息可以这样输入thread ID | grep 'main('

Arthas和常量池,jvm,java,开发语言

 thread -b查看死锁

Arthas和常量池,jvm,java,开发语言

反编译查看代码是否是最新代码输入jad demo.MathGame

Arthas和常量池,jvm,java,开发语言

 查看关键方法返回值,比如查看demo.Math类下面primeFactors方法的返回值可以输入watch demo.MathGame primeFactors returnObj

Arthas和常量池,jvm,java,开发语言

二、gc日志

对于java应用我们可用通过一些配置把程序运行过程中的gc日志全部打印出来,然后分析gc日志得到关键性指标,分析gc原因,调优参数,打印gc日志方法,在jvm参数里增加参数打印到当前目录下gc-%t.log文件中,%t代表时间,-XX:NumberOfGCLogFiles=10参数表示会生成10个gc日志文件,-XX:GCLogFileSize=100M参数表示gc日志文件大小超过100M会重新另起一个gc日志文件存储

-Xloggc:./gc-%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps  -XX:+PrintGCTimeStamps -XX:+PrintGCCause  
-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M

运行程序时加上gc打印参数

java -jar -Xloggc:./gc-%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps  -XX:+PrintGCTimeStamps -XX:+PrintGCCause -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M microservice-eureka-server.jar

Arthas和常量池,jvm,java,开发语言

 我们可以看到图中第一行红框,是项目的配置参数。这里不仅配置了打印GC日志,还有相关的VM内存参数

第二行红框中的是在这个GC时间点发生GC之后相关GC情况。

  1. 对于2.668: 这是从jvm启动开始计算到这次GC经过的时间,前面还有具体的发生时间日期。
  2. Full GC(Metadata GC Threshold)指这是一次full gc,括号里是gc的原因, PSYoungGen是年轻代的GC,ParOldGen是老年代的GC,Metaspace是元空间的GC
  3. 3515K->0K(265216K),这三个数字分别对应GC之前占用年轻代的大小,GC之后年轻代占用,以及整个年轻代的大小。
  4. 3564K->6681K(101376K),这三个数字分别对应GC之前占用老年代的大小,GC之后老年代占用,以及整个老年代的大小。
  5. 7080K->6681K(366592K),这三个数字分别对应GC之前占用堆内存的大小,GC之后堆内存占用,以及整个堆内存的大小。
  6. 20889K->20889K(1069056K),这三个数字分别对应GC之前占用元空间内存的大小,GC之后元空间内存占用,以及整个元空间内存的大小。
  7. 0.0408131是该时间点GC总耗费时间。

 从日志可以发现几次fullgc都是由于元空间不够导致的,所以我们可以将元空间调大点

java -jar -Xloggc:./gc-adjust-%t.log -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+PrintGCDetails -XX:+PrintGCDateStamps  
-XX:+PrintGCTimeStamps -XX:+PrintGCCause  -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M 
microservice-eureka-server.jar

 调整完我们再看下gc日志发现已经没有因为元空间不够导致的fullgc了

对于CMS和G1收集器的日志会有一点不一样,也可以试着打印下对应的gc日志分析下,可以发现gc日志里面的gc步骤跟我们之前讲过的步骤是类似的

/**
 * 把堆内存设置小点 让堆内存溢出
 */
public class HeapTest {

    byte[] a = new byte[1024 * 100];  //100KB

    public static void main(String[] args) throws InterruptedException {
        ArrayList<HeapTest> heapTests = new ArrayList<>();
        while (true) {
            heapTests.add(new HeapTest());
            Thread.sleep(10);
        }
    }
}

CMS,运行时添加如下参数,gc日志放到d盘

-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

 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

上面的这些参数,能够帮我们查看分析GC的垃圾收集情况。但是如果GC日志很多很多,成千上万行。就算你一目十行,看完了,脑子也是一片空白。所以我们可以借助一些功能来帮助我们分析,这里推荐一个gceasy(https://gceasy.io),可以上传gc文件,然后他会利用可视化的界面来展现GC情况。具体下图所示 

Arthas和常量池,jvm,java,开发语言

上图我们可以看到年轻代,老年代,以及永久代的内存分配,和最大使用情况。

 Arthas和常量池,jvm,java,开发语言

上图我们可以看到堆内存在GC之前和之后的变化,以及其他信息。 

这个工具还提供基于机器学习的JVM智能优化建议,当然现在这个功能需要付费

jvm参数汇总查看命令

java -XX:+PrintFlagsInitial #表示打印出所有参数选项的默认值
java -XX:+PrintFlagsFinal #表示打印出所有参数选项在运行程序时生效的值

三、常量池

1、Class常量池和运行时常量池

Class常量池可以理解为是Class文件中的资源仓库。 Class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic References)。

一个class文件的16进制大体结构如下图:

Arthas和常量池,jvm,java,开发语言

 对应的含义,细节可以查下oracle官方文档

当然我们一般不会去人工解析这种16进制的字节码文件,我们一般可以通过javap命令生成更可读的JVM字节码指令文件:

javap -v HeapTest.class

Arthas和常量池,jvm,java,开发语言

 红框标出的就是class常量池信息,常量池中主要存放两大类常量:字面量和符号引用。

  • 字面量

字面量就是指由字母、数字等构成的字符串或者数值常量

字面量只可以右值出现,所谓右值是指等号右边的值,如:int a=1 这里的a为左值,1为右值。在这个例子中1就是字面量。

int a = 1;
int b = 2;
int c = "abcdefg";
int d = "abcdefg";
  • 符号引用

符号引用是编译原理中的概念,是相对于直接引用来说的。主要包括了以下三类常量:

  1. 类和接口的全限定名 
  2. 字段的名称和描述符 
  3. 方法的名称和描述符

上面的a,b就是字段名称,就是一种符号引用,还有Math类常量池里的 Lcom/tuling/jvm/Math 是类的全限定名,main和compute是方法名称,()是一种UTF8格式的描述符,这些都是符号引用。

这些常量池现在是静态信息,只有到运行时被加载到内存后,这些符号才有对应的内存地址信息,这些常量池一旦被装入内存就变成运行时常量池,对应的符号引用在程序加载或运行时会被转变为被加载到内存区域的代码的直接引用,也就是我们说的动态链接了。例如,compute()这个符号引用在运行时就会被转变为compute()方法具体代码在内存中的地址,主要通过对象头里的类型指针去转换直接引用。

2、字符串常量池

Arthas和常量池,jvm,java,开发语言

字符串常量池的设计思想

  1. 字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能
  2. JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
  • 为字符串开辟一个字符串常量池,类似于缓存区
  • 创建字符串常量时,首先查询字符串常量池是否存在该字符串
  • 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中

三种字符串操作(Jdk1.7 及以上版本)

  • 直接赋值字符串

String s = "rufeng"; // s指向常量池中的引用

这种方式创建的字符串对象,只会在常量池中。

因为有"rufeng"这个字面量,创建对象s的时候,JVM会先去常量池中通过 equals(key) 方法,判断是否有相同的对象

如果有,则直接返回该对象在常量池中的引用;

如果没有,则会在常量池中创建一个新对象,再返回引用。

  • new String();
String s1 = new String("rufeng"); // s1指向内存中的对象引用

这种方式会保证字符串常量池和堆中都有这个对象,没有就创建,最后返回堆内存中的对象引用。

步骤大致如下:

因为有"rufeng"这个字面量,所以会先检查字符串常量池中是否存在字符串"rufeng"

不存在,先在字符串常量池里创建一个字符串对象;再去内存中创建一个字符串对象"rufeng";

存在的话,就直接去堆内存中创建一个字符串对象"rufeng";

最后,将内存中的引用返回。

  • intern方法
String s1 = new String("rufeng");   
String s2 = s1.intern();

System.out.println(s1 == s2);  //false

String中的intern方法是一个 native 的方法,当调用 intern方法时,如果池已经包含一个等于此String对象的字符串(用equals(oject)方法确定),则返回池中的字符串。否则,将intern返回的引用指向当前字符串 s1(jdk1.6版本需要将 s1 复制到字符串常量池里)。

字符串常量池位置

Jdk1.6及之前: 有永久代, 运行时常量池在永久代,运行时常量池包含字符串常量池

Jdk1.7:有永久代,但已经逐步“去永久代”,字符串常量池从永久代里的运行时常量池分离到堆里

Jdk1.8及之后: 无永久代,运行时常量池在元空间,字符串常量池里依然在堆里

用一个程序证明下字符串常量池在哪里:

/**
 * jdk6:-Xms6M -Xmx6M -XX:PermSize=6M -XX:MaxPermSize=6M  
 * jdk8:-Xms6M -Xmx6M -XX:MetaspaceSize=6M -XX:MaxMetaspaceSize=6M
 */
public class RuntimeConstantPoolOOM{
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000000; i++) {
            String str = String.valueOf(i).intern();
            list.add(str);
        }
    }
}

运行结果:
jdk7及以上:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
jdk6:Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

字符串常量池设计原理

  字符串常量池底层是hotspot的C++实现的,底层类似一个 HashTable, 保存的本质上是字符串对象的引用。

看一道比较常见的面试题,下面的代码创建了多少个 String 对象?

String s1 = new String("he") + new String("llo");
String s2 = s1.intern();
 
System.out.println(s1 == s2);
// 在 JDK 1.6 下输出是 false,创建了 6 个对象
// 在 JDK 1.7 及以上的版本输出是 true,创建了 5 个对象
// 当然我们这里没有考虑GC,但这些对象确实存在或存在过
示例一:
String s0="rufeng";
String s1="rufeng";
String s2="ru" + "feng";
System.out.println( s0==s1 ); //true
System.out.println( s0==s2 ); //true

分析:字符串"ru" + "feng"在编译时就被优化成"rufeng",s2只是指向常量池的引用,一共在字符串常量池创建了三个对象"ru","feng","rufeng"

示例二
String s0="rufeng";
String s1=new String("rufeng");
String s2="ru" + new String("feng");
System.out.println( s0==s1 );  // false
System.out.println( s0==s2 );  // false
System.out.println( s1==s2 );  // false

分析:s0是指向字符串常量池的引用,s1则是在堆中创建了新的对象,s2最后在字符串常量池中找到并指向字符串常量池"rufeng"对应的值,jdk1.7下一共创建6对象(s0在字符串常量池中,s1在堆中,s2的"ru","feng"在字符串常量池中,s2的new String("feng")在堆中,s2是字新创建了一个对象)

示例三
String a = "a1";
String b = "a" + 1;
System.out.println(a == b); // true 
  
String a = "atrue";
String b = "a" + "true";
System.out.println(a == b); // true 
  
String a = "a3.4";
String b = "a" + 3.4;
System.out.println(a == b); // true

分析:加号在编译时就已经优化连接成字符串了

示例四
String a = "ab";
String bb = "b";
String b = "a" + bb;

System.out.println(a == b); // false

分析:"a"+变量或者"a"+new String("xx"),编译时都不能优化

示例五
String a = "ab";
final String bb = "b";
String b = "a" + bb;

System.out.println(a == b); // true

分析:final定义的字符串不可被修改,编译时可以解析成一个常量,都是指向字符串常量池的引用,所以是相等的

示例六
String a = "ab";
final String bb = getBB();
String b = "a" + bb;

System.out.println(a == b); // false

private static String getBB() 
{  
    return "b";  
 }

分析:getBB()方法在编译时无法确定返回何值,只有真的在运行时调用方法getBB()才会给"b"分配内存空间,重新创建一个新的String

示例七
//字符串常量池:"计算机"和"技术"     堆内存:str1引用的对象"计算机技术"  
//堆内存中还有个StringBuilder的对象,但是会被gc回收,StringBuilder的toString方法会new String(),这个String才是真正返回的对象引用
String str2 = new StringBuilder("计算机").append("技术").toString();   //没有出现"计算机技术"字面量,所以不会在常量池里生成"计算机技术"对象
System.out.println(str2 == str2.intern());  //true
//"计算机技术" 在池中没有,但是在heap中存在,则intern时,会直接返回该heap中的引用

//字符串常量池:"ja"和"va"     堆内存:str1引用的对象"java"  
//堆内存中还有个StringBuilder的对象,但是会被gc回收,StringBuilder的toString方法会new String(),这个String才是真正返回的对象引用
String str1 = new StringBuilder("ja").append("va").toString();    //没有出现"java"字面量,所以不会在常量池里生成"java"对象
System.out.println(str1 == str1.intern());  //false
//java是关键字,在JVM初始化的相关类里肯定早就放进字符串常量池了

String s1=new String("test");  
System.out.println(s1==s1.intern());   //false
//"test"作为字面量,放入了池中,而new时s1指向的是heap中新生成的string对象,s1.intern()指向的是"test"字面量之前在池中生成的字符串对象

String s2=new StringBuilder("abc").toString();
System.out.println(s2==s2.intern());  //false
//同上

3、八种基本类型的包装类和对象池

java中基本类型的包装类的大部分都实现了常量池技术(严格来说应该叫对象池,在堆上),这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用对象池,也即对象不负责创建和管理大于127的这些类的对象。因为一般这种比较小的数用到的概率相对较大。文章来源地址https://www.toymoban.com/news/detail-561659.html

public class Test {

    public static void main(String[] args) {
        //5种整形的包装类Byte,Short,Integer,Long,Character的对象,  
        //在值小于127时可以使用对象池  
        Integer i1 = 127;  //这种调用底层实际是执行的Integer.valueOf(127),里面用到了IntegerCache对象池
        Integer i2 = 127;
        System.out.println(i1 == i2);//输出true  

        //值大于127时,不会从对象池中取对象  
        Integer i3 = 128;
        Integer i4 = 128;
        System.out.println(i3 == i4);//输出false  
        
        //用new关键词新生成对象不会使用对象池
        Integer i5 = new Integer(127);  
        Integer i6 = new Integer(127);
        System.out.println(i5 == i6);//输出false 

        //Boolean类也实现了对象池技术  
        Boolean bool1 = true;
        Boolean bool2 = true;
        System.out.println(bool1 == bool2);//输出true  

        //浮点类型的包装类没有实现对象池技术  
        Double d1 = 1.0;
        Double d2 = 1.0;
        System.out.println(d1 == d2);//输出false  
    }
}

到了这里,关于Arthas和常量池的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 深度剖析JVM调优法则,神器Arthas从CPU/内存出发轻松掌握调优实战技巧

    场景一、CPU过高 CPU占用过高排查思路: step1:进行arthas step2:输入deashboard 如何不记得命令可以在控制台输入help step3:查看线程栈信息 从线程栈信息中定位到具体的java代码。 场景二、内存占用过高 内存占用过高排查思路: step1:进行arthas step2:输入deashboard 在dashboard页面中

    2024年02月06日
    浏览(45)
  • JVM的内存分配及各种常量池的区别(静态常量池、运行时常量池、字符串常量池)

    先了解下JVM中的内存分配,此处以hotspot vm为例(官方jdk采用的vm) 堆内存是各个线程共享的区域 它用于存储已经被虚拟机加载的类信息、常量、静态变量、即编译器编译后的代码等数据。静态变量、常量在方法区,所有方法,包括静态和非静态的,也在方法区 这里解释一下方法

    2023年04月14日
    浏览(52)
  • JVM 常量池、即时编译与解析器、逃逸分析

    常量池底层使用HashTable key 是字符串和长度生成的hashValue,然后再hash生成index, 该index就是key;Value是一个HashTableEntry; 1、key     hashValue = hash string(name, len)     index = hash to index(hashValue);     1、根据字符串(即 name)以及字符串的长度计算出hashValue     2、根据hashValue计算出

    2024年02月11日
    浏览(50)
  • JVM运行时数据区——字符串常量池位置的调整

            在JDK6及之前,使用永久代来实现方法区,字符串常量池(StringTable)是在 永久代(方法区)中 的,但是方法区的回收效率不高,在Full GC时才会回收。           在JDK7中,将字符串常量池 转移到了堆中 ,分配在年轻代和老年代中。         在JDK8中,为了 融合

    2024年02月16日
    浏览(33)
  • Java 诊断利器 Arthas使用

    Arthas是一款阿里巴巴开源的 Java 线上诊断工具,功能非常强大,可以解决很多线上不方便解决的问题。 Arthas诊断使用的是命令行交互模式,支持JDK6+,Linux、Mac、Windows 操作系统,命令还支持使用  tab  键对各种信息的自动补全,诊断起来非常利索。 官网:https://arthas.aliyun.

    2024年02月07日
    浏览(64)
  • c语言中指针常量和常量指针

    指针常量(Pointer to Constant)和常量指针(Constant Pointer)是C和C++中用于描述指针和常量的组合概念。 指针常量本质是一个常量,它的值不能改变,即指针始终指向同一个地址。但通过指针可以修改所指向对象的值。 int* const ptr;//表示ptr是一个指针常量,指向一个整型常量对

    2024年02月10日
    浏览(36)
  • JVM执行引擎——为什么Java是半编译半解释语言

            起初设计者的初衷是将字节码文件翻译为机器语言的指令来执行即可,就诞生了解释器。但是采用一行行来解释的 效率比较低 ,JIT编译器会将编译后的机器码做一个缓存的操作,放在方法区的JIT代码缓存中,是否需要启用JIT编译器直接将字节码编译为机器码,则

    2024年02月15日
    浏览(49)
  • 阿里开源的java而分析工具(arthas)

    1、官网地址:https://alibaba.github.io/arthas/quick-start.html 2、安装 wget https://alibaba.github.io/arthas/arthas-boot.jar java -jar arthas-boot.jar 发生这个问题的原因有两个: 一个是目前机器中没有安装Oracle的jdk; 一个是没有java程序运行 解决方案: 1.卸载openJDK  安装Oracle的jdk  参加地址:https://

    2024年02月04日
    浏览(37)
  • Java开发环境简介(JDK、JRE、JVM)

    目录 1、Java开发环境 2、JDK和JRE 3、JDK下载和安装 3.1 下载 3.2 安装 3.3 配置path环境变量 JDK8配置方案1:只配置path ⭐JDK8配置方案2:配置JAVA_HOME+path(推荐) path配置小结 JDK17配置方案:自动配置 4、Java核心机制:JVM 补充:Java字节码 JVM的优点 JVM的缺点 JVM的运行过程 5、Java程序

    2024年02月21日
    浏览(45)
  • Java开发 - 你不知道的JVM优化详解

    代码上的优化达到一定程度,再想提高系统的性能就很难了,这时候,优秀的程序猿往往会从JVM入手来进行系统的优化。但话说回来,JVM方面的优化也是比较危险的,如果单单从测试服务器来优化JVM是没有太大的意义的,不同的服务器即使环境相同,访问流量方面也是不一样

    2024年02月07日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包