借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)

本文使用环境jdk11+win10
首先贴实验代码,JVM参数设置为:-Xmx10m -XX:+UseSerialGC -XX:-UseCompressedOops

package test4;



public class JHSDB_TestCse {
    //-Xmx10m -XX:+UseSerialGC -XX:-UseCompressedOops
    //
    static class Test{
        int k = 2;
        String s = "ssss";
        String ss = "ssss";
        String sss = new String("ssss");
        String ssss = new String("sssss");
        String sssss = new String("sssss");
        String ssssss = "sssss";
        Integer i = Integer.valueOf(25);
        Integer i1 = Integer.valueOf(25);
        Integer i10 = Integer.valueOf(127);
        Integer i11 = Integer.valueOf(127);
        Integer i2 = Integer.valueOf(128);
        Integer i3 = Integer.valueOf(128);
        Integer i4 = Integer.valueOf(-128);
        Integer i5 = Integer.valueOf(-128);
        Integer i6 = Integer.valueOf(0);
        Integer i7 = Integer.valueOf(0);
        Integer i8 = Integer.valueOf(-129);
        Integer i9 = Integer.valueOf(-129);
        static ObjectHolder staticObj = new ObjectHolder();
        ObjectHolder instanceObj = new ObjectHolder();
        void foo(){
            ObjectHolder localObj = new ObjectHolder();
            System.out.println("deon");
        }

    }
    private static class ObjectHolder{}

    public static void main(String[] args) {
        Test test = new Test();
        test.foo();
    }
}

0.初始

在终端使用jsp-l 查看当前运行程序的端口(可以使用断点使程序中断,这里我们在System.out.println("deon");处中断。)
然后使用jhsd b hsdb --pid 端口号 打开jhsdb
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第1张图片
(下头那几个框框,刚打开是没有的)

点击如下命令,查看堆的情况。
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第2张图片借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第3张图片

算一下可以知道,堆的总大小与我们设置是吻合的。堆的大小为10M,且老年代与新生代的比例为2:1 ,默认eden:Survivor = 8:1
然后我们打开Console ,输入查找某类型的对象的命令。

1.对象

借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第4张图片
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第5张图片
阔以发现对象新创建是创建于年轻代的eden区。
然后再打开Inspector ,查看在堆中,该对象的存储信息
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第6张图片
输入地址查看存放的对象借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第7张图片
注意看,类的基本类型的静态域是存于堆的啊 (其实这就是写这篇文章的原因,有人在问) 。大家来猜猜,最后一个ObjectHolder 是静态域还是实例域 (假设大家英语不好)
好嘛事实上,它是实例域
再打开命令行 (windows-Console) ,输入revptrs 地址 反查找引用它的是谁
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第8张图片
这时你就会发现一个熟悉的地址。正是我们刚才那个Test实例对象。
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第9张图片
细心的朋友阔能发现不对劲了,那个静态域static ObjectHolder staticObj 去哪了?
来让我们找找,打开Object Histogram ,看看整个程序都生成了哪些对象
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第10张图片
输入对象的名称点击望远镜,选中。再点击放大镜借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第11张图片
依次选中这三个,分别点击Shwo Liveness Path借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第12张图片
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第13张图片
可以发现,从上往下。
第一个对象被Test引用,它便是那个实例域
第二个对象没有被堆中任何变量引用,它便是foo中的局部变量
第三个对象,被Test的Class引用,它便是静态域
我们还可以用revptrs命令 查看一下引用静态域对象的到底是不是Class。(比较Class对象的地址说明其存在于堆中)
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第14张图片
还可以看看引用Class的是谁…(疯狂套娃)借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第15张图片
那么那个局部变量被谁所引用呢?
选中main线程,再点击如图所示图标,打开栈内存情况
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第16张图片
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第17张图片
果然存在是被栈所引用的。

2 .String

再回到梦想开始的地方。
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第18张图片
有没有发现有些地址是重复的呀,规律就是只要不是new String…只要前面的字符已经存在过,那么对象的地址一定是第一个""对象的地址。这咋回事呢?
相信大家都知道一个说法,如果使用"" 创建字符串,那么它会首先在字符串常量池中寻找,如果没有值相等的量,才会在常量池中创建,然后引用指向那个常量,如存在了就只会指向那个常量,而使用new String()无论字符串常量池中是否存在该字符串,都会在上创建,如果字符串常量池不存在,再在常量池中创建一个。那么到底是不是呢?
首先从地址上观察这些对象都存在堆内,这是毋庸置疑的。
好像有点不一样噢,大家都在Eden区。(jdk7以后已经将字符串常量池y移置堆中)
让我们再看看Test类的常量池。
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第19张图片
可以看到该类的方法信息,注意这些是存在方法区中的(观察地址),一直翻到最后,进入常量池借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第20张图片
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第21张图片
可以看到 “ssss” 字段和 “sssss” 字段就存在于这里了,如果再往下找,还会发现我们写在println语句中创建的 "deon"
还会发现常量池里还存了
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第22张图片
本来我以为这是字段名称
然后我又发现了它,本来或许凑巧是内部的字面量呢,但是好像又不可能,我把单词拼错了,应该不会有。等最后加个汉字再测试一下。
在这里插入图片描述
通过上述资料,我做出大概猜测类型常量池里存的应该是指向字符常量池的指针,因为该常量池的地址位于堆之外,在方法区。字符串常量池会在编译期间将所有""存起来,如果再new String("")的话就会产生新的对象。那么new String()操作就可能产生1~2个对象,肯定会产生的是运行期间在堆中分配的对象,另一个则是编译期间生成的对象(如果常量池里没有的话),而"" 仅会在编译器产生对象在字符串常量池(如果常量池里没有的话)。
以上说法有误,见补充。

补充:

//仅截取关键部分代码
	String y = "源大彪";
	void foo(){
          y = new String("达标");
    }
     public static void main(String[] args) {
    Test test = new Test();
     ···test.foo();
    ····String y = "源大彪";
        System.out.println(y);
    }

这是执行到y = new String("达标");前的地址
在这里插入图片描述
这是执行到y = new String("达标");后的地址
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第23张图片
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第24张图片
栈上引用的仍然是字符串常量池的地址。
我还发现一个有意思的细节,就算是用new String() 包装过的对象也仅仅是对之前常量池的对象进行了一个浅复制。它们里面用来存储String字符的byte数组都是同一个。(下图与上图不是一次实验)
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第25张图片
再用这种方式来验证一下是否会在常量池中存储
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第26张图片
注意此时生成的对象里并没有以下所有字符串的
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第27张图片
运行完System.out.println("done"); dnoe就出现了!不是说编译期生成""对象吗借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第28张图片
我们再看看当前类的常量池就会惊奇的发现,还没有运行到的地方已经生成了,由此可说明常量池中的字符串与堆中字符串并非同一字符串。
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第29张图片
也就是说编译期间所有的""都会在常量池中产生字面量,运行时当有引用尝试用""产生对象时,则会在堆中生成一个对象,再用一个指针指向堆中对象,下次再有引用尝试时,则直接将引用指向堆中对象,也就是说""这种方式会创建0~1(如果没有重排序的话就是0个或者2个)。而new String("")的方式则会创建1~2个(如果不重排序的话,1个或者3个)。其他方式new String() 则会创建1个对象。
详细原理

3.Intger

其实看源码也能知道-127~128的范围会被缓存。
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第30张图片
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第31张图片

这里也证实了这一点。不过注意,被缓存的对象在老年代里。而且常量池并没有被堆中和栈中任何对象引用,但是它的状态仍然是存活那就只能在被方法区的引用了。(下图与之前不是一次运行结果)
借助jhsdb来看看对象存在于何处以及Integer类型和String类型的缓存机制(详细得一批)_第32张图片

下面是Integer缓存的源码。可以发现范围默认为-128~127,可以通过参数设置来修改integerCacheHighPropValue 修改范围。

private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

你可能感兴趣的:(JVM)