java基础-12 jvm

JVM

目录

JVM

什么是 JVM ?

比较: jvm jre jdk

常见的 JVM

学习路线

JVM内存结构

1. 程序计数器

2. 虚拟机栈

问题辨析

JVM调优总结 -Xms -Xmx -Xmn -Xss

栈内存溢出

线程运行诊断

本地方法栈

堆内存溢出

案例 垃圾回收后,内存占用仍然很高

方法区

方法区组成

方法区内存溢出

运行时常量池

StringTable

StringTable 特性

StringTable 位置

StringTable 垃圾回收

StringTable 性能调优

直接内存

分配和回收原理


 

什么是 JVM ?

定义: Java Virtual Machine - java 程序的运行环境(java 二进制字节码的运行环境)

好处:

  1. 一次编写,跨平台运行
  2. 自动内存管理,垃圾回收功能
  3. 数组下标越界检查
  4. 多态

比较: jvm jre jdk

  • 常见的 JVM

  • 学习路线

  • JVM内存结构

  • 1. 程序计数器

  • 定义 Program Counter Register 程序计数器(寄存器)
  • 作用,是记住下一条jvm指令的执行地址
  • 特点 每个线程都有自己的程序计数器,是线程私有的 不会存在内存溢出
  • 2. 虚拟机栈

  • Java Virtual Machine Stacks (Java 虚拟机栈)
  • 定义
  1. 每个线程运行时所需要的内存,称为虚拟机栈
  2. 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  3. 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
  4. 活动栈帧是每个栈顶端的栈帧
  • 问题辨析

1. 垃圾回收是否涉及栈内存?

不涉及,栈内存在每个方法调用完成之后会自动释放内存,根本不需要垃圾回收来管理栈内存

2. 栈内存分配越大越好吗?

不是,JDK5.0以后每个线程堆栈大小为1M,在相同物理内存下,减小这个值能生成更多的线程,所以栈内存分配越大线程数就会越小,该值一般使用默认值

3. 方法内的局部变量是否线程安全?

如果方法内局部变量没有逃离方法的作用访问,它是线程安全的

如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全

  • 相关 VM 参数

含义

参数

堆初始大小

-Xms

堆最大大小

-Xmx 或 -XX:MaxHeapSize=size

新生代大小

-Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size )

幸存区比例(动态)

-XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy

幸存区比例

-XX:SurvivorRatio=ratio

晋升阈值

-XX:MaxTenuringThreshold=threshold

晋升详情

-XX:+PrintTenuringDistribution

GC详情

-XX:+PrintGCDetails -verbose:gc

FullGC 前 MinorGC

-XX:+ScavengeBeforeFullGC

  • JVM调优总结 -Xms -Xmx -Xmn -Xss

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM初始内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0

  • 栈内存溢出

  1. 栈帧过多导致栈内存溢出:常见的有方法的递归调用可能会导致栈帧过多而栈内存溢出
  2. 栈帧过大导致栈内存溢出:  常见的有生成集合时循环引用(例如:生成json时存入学生对象,属性课程中也引用学生对象这样循环引用)
  • 线程运行诊断

  • 定位
  1. 用top定位哪个进程对cpu的占用过高
  2. ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
  3. jstack 进程id 可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号

 

  • 案例2:程序运行很长时间没有结果

死锁:互相等待

  • 本地方法栈

jvm调用本地方法时需要的内存空间(本地方法是指不是由java代码编写的一些方法,Object类中有很多本地方法:clone()、hashCode()、notify()、notifyAll()、wait()等方法)

  • 定义  Heap 堆
  • 通过 new 关键字,创建对象都会使用堆内存

特点

  1. 它是线程共享的,堆中对象都需要考虑线程安全的问题
  2. 有垃圾回收机制
  • 堆内存溢出

一直创建对象就会造成堆内存溢出

  • 堆内存诊断

1. jps 工具 查看当前系统中有哪些 java 进程

2. jmap 工具 查看堆内存占用情况 jmap - heap 进程id

3. jconsole 工具 图形界面的,多功能的监测工具,可以连续监测

 右键管理员运行

/**
 * 演示堆内存溢出 java.lang.OutOfMemoryError: Java heap space
 * -Xmx8m
 */
public class Demo1_5 {

    public static void main(String[] args) {
        int i = 0;
        try {
            List<String> list = new ArrayList<>();
            String a = "hello";
            while (true) {
                list.add(a); // hello, hellohello, hellohellohellohello ...
                a = a + a;  // hellohellohellohello
                i++;
                Thread.sleep(50);
            }
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println(i);
        }
    }
}



/*
java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3332)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
	at java.lang.StringBuilder.append(StringBuilder.java:136)
	at cn.itcast.jvm.t1.heap.Demo1_5.main(Demo1_5.java:19)
26

Process finished with exit code 0
*/

 

  • 案例 垃圾回收后,内存占用仍然很高

jvisualvm

  • 方法区

  • 定义

参考:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html

Java虚拟机具有一个在所有Java虚拟机线程之间共享的方法区域。该方法区域类似于常规语言的编译代码的存储区域,或者类似于操作系统过程中的“文本”段。它存储每个类的结构,例如运行时常量池,字段和方法数据,以及方法和构造函数的代码,包括用于类和实例初始化以及接口初始化的特殊方法(第2.9节)。

方法区域是在虚拟机启动时创建的。尽管方法区域在逻辑上是堆的一部分,但是简单的实现可以选择不进行垃圾回收或压缩。该规范没有规定方法区域的位置或用于管理已编译代码的策略。方法区域可以是固定大小的,或者可以根据计算的需要进行扩展,如果不需要更大的方法区域,则可以缩小。方法区域的内存不必是连续的。

Java虚拟机实现可以为程序员或用户提供对方法区域初始大小的控制,以及在方法区域大小可变的情况下,可以控制最大和最小方法区域大小。

以下异常条件与方法区域相关联:

  • 如果无法提供方法区域中的内存来满足分配请求,则Java虚拟机将抛出一个OutOfMemoryError

  • 方法区组成

  • 方法区内存溢出

1.8 以前会导致永久代内存溢出

1.8 之后会导致元空间内存溢出

直接运行以下代码一般不会出现元空间内存溢出,因为本地电脑内存一般都很大,需要看到元空间内存溢出,可以把元空间内存大小 -XX:MaxMetaspaceSize=8m 设置小一点

/**
 * 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace
 * 需要设置元空间内存大小   -XX:MaxMetaspaceSize=8m
 */
public class Demo1_8 extends ClassLoader { // 可以用来加载类的二进制字节码
    public static void main(String[] args) {
        int j = 0;
        try {
            Demo1_8 test = new Demo1_8();
            for (int i = 0; i < 10000; i++, j++) {
                // ClassWriter 作用是生成类的二进制字节码
                ClassWriter cw = new ClassWriter(0);
                // 版本号, public, 类名, 包名, 父类, 接口
                cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
                // 返回 byte[]
                byte[] code = cw.toByteArray();
                // 执行了类的加载
                test.defineClass("Class" + i, code, 0, code.length); // Class 对象
            }
        } finally {
            System.out.println(j);
        }
    }
}

演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace * -XX:MaxMetaspaceSize=8m

Exception in thread "main" java.lang.OutOfMemoryError: Metaspace

  • 方法区内存溢出场景

* spring

* mybatis

.class二进制文件

文件内容

idea处理后

。java文件

  • 利用jdk 的javap反编译 .class文件
javap -v HelloWorld.class

 

Microsoft Windows [版本 10.0.18362.778]
(c) 2019 Microsoft Corporation。保留所有权利。

E:\BaiduNetdiskDownload\解密JVM虚拟机底层原理\代码\jvm\jvm\out\production\jvm\cn\itcast\jvm\t5>javap -v HelloWorld.class
Classfile /E:/BaiduNetdiskDownload/解密JVM虚拟机底层原理/代码/jvm/jvm/out/production/jvm/cn/itcast/jvm/t5/HelloWorld.class
  Last modified 2019年7月13日; size 567 bytes
  MD5 checksum 2a2ae032409477af67db8b548e0ef913
  Compiled from "HelloWorld.java"
public class cn.itcast.jvm.t5.HelloWorld
  minor version: 0
  major version: 52
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #5                          // cn/itcast/jvm/t5/HelloWorld
  super_class: #6                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            // hello world
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // cn/itcast/jvm/t5/HelloWorld
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcn/itcast/jvm/t5/HelloWorld;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWorld.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               hello world
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               cn/itcast/jvm/t5/HelloWorld
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public cn.itcast.jvm.t5.HelloWorld();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 4: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcn/itcast/jvm/t5/HelloWorld;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 6: 0
        line 7: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

E:\BaiduNetdiskDownload\解密JVM虚拟机底层原理\代码\jvm\jvm\out\production\jvm\cn\itcast\jvm\t5>
E:\BaiduNetdiskDownload\解密JVM虚拟机底层原理\代码\jvm\jvm\out\production\jvm\cn\itcast\jvm\t5>

 

  • 运行时常量池

  1. 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量 等信息
  2. 运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量 池,并把里面的符号地址变为真实地址
  • StringTable

先看几道面试题:

  •  可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池

        1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串 池中的对象返回

        1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份, 放入串池, 会把串池中的对象返回

// 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)
public class HelloWorld {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b";
        String s4 = s1 + s2;
        String s5 = "ab";
        String s6 = s4.intern();
        // 问
        System.out.println(s3 == s4);       //false
        System.out.println(s3 == s5);       //true
        System.out.println(s3 == s6);       //true
        String x2 = new String("c") + new String("d");
        String x1 = "cd";
        x2.intern();
        // 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
        System.out.println(x1 == x2);        //false

        String x3 = new String("e") + new String("f");
        String x4 = x3.intern();
        String x5 = "ef";
        System.out.println(x3 == x4);        //true
        System.out.println(x3 == x5);        //true
    }
}
public class HelloWorld {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b";
        String s4 = s1 + s2;
        System.out.println(s3 == s4);
        /*
        @Override
        public String toString() {
            // Create a copy, don't share the array
            return new String(value, 0, count);
        }
        */
    }
}

/*
false
*/

反编译后的代码

public class HelloWorld {
    public HelloWorld() {
    }

    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        String s4 = s1 + s2;  //(new StringBuilder()).append(s1).append(s2).toString();
        System.out.println(s3 == s4);
    }
}
  • 案例

.java文件

package cn.itcast.jvm.t5;

// 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)
public class HelloWorld {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        String s4 = "a" + "b";
        String s5 = s1 + s2;
        String s6 = "c" + s1;
        String s7 = s1 + "c";
    }
}
  • .class文件
public class HelloWorld {
    public HelloWorld() {
    }

    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        String s4 = "ab";
        (new StringBuilder()).append(s1).append(s2).toString();
        (new StringBuilder()).append("c").append().toString();
        String s7 = s1 + "c";  //(new StringBuilder()).append(s1).append("c").toString();
    }
}

 

  • StringTable 特性

  • 常量池中的字符串仅是符号,第一次用到时才变为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是 StringBuilder (1.8)
  • 字符串常量拼接的原理是编译期优化
  • 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池

       1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串 池中的对象返回

       1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份, 放入串池, 会把串池中的对象返回

    public static void main(String[] args) {

        String x = "ab";   //StringTable   ["ab"]
        String s = new String("a") + new String("b");  //StringTable  ["ab", "a", "b"]

        // 堆  new String("a")   new String("b")    new String("ab")
        String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回

        System.out.println( s2 == x);           //true
        System.out.println( s == x );           //false
    }

}
  • StringTable 位置

  • StringTable 垃圾回收

/**
 * 演示 StringTable 垃圾回收
 * -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
 */
public class Demo1_7 {
    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        try {
            
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println(i);
        }
    }
}


/*
E:\software\jdk1.8.0_111\bin\java.exe -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc "-javaagent:E:\software\IntelliJ IDEA 2019.2.3\lib\idea_rt.jar=1315:E:\software\IntelliJ IDEA 2019.2.3\bin" -Dfile.encoding=UTF-8 -classpath E:\software\jdk1.8.0_111\jre\lib\charsets.jar;E:\software\jdk1.8.0_111\jre\lib\deploy.jar;E:\software\jdk1.8.0_111\jre\lib\ext\access-bridge-64.jar;E:\software\jdk1.8.0_111\jre\lib\ext\cldrdata.jar;E:\software\jdk1.8.0_111\jre\lib\ext\dnsns.jar;E:\software\jdk1.8.0_111\jre\lib\ext\jaccess.jar;E:\software\jdk1.8.0_111\jre\lib\ext\jfxrt.jar;E:\software\jdk1.8.0_111\jre\lib\ext\localedata.jar;E:\software\jdk1.8.0_111\jre\lib\ext\nashorn.jar;E:\software\jdk1.8.0_111\jre\lib\ext\sunec.jar;E:\software\jdk1.8.0_111\jre\lib\ext\sunjce_provider.jar;E:\software\jdk1.8.0_111\jre\lib\ext\sunmscapi.jar;E:\software\jdk1.8.0_111\jre\lib\ext\sunpkcs11.jar;E:\software\jdk1.8.0_111\jre\lib\ext\zipfs.jar;E:\software\jdk1.8.0_111\jre\lib\javaws.jar;E:\software\jdk1.8.0_111\jre\lib\jce.jar;E:\software\jdk1.8.0_111\jre\lib\jfr.jar;E:\software\jdk1.8.0_111\jre\lib\jfxswt.jar;E:\software\jdk1.8.0_111\jre\lib\jsse.jar;E:\software\jdk1.8.0_111\jre\lib\management-agent.jar;E:\software\jdk1.8.0_111\jre\lib\plugin.jar;E:\software\jdk1.8.0_111\jre\lib\resources.jar;E:\software\jdk1.8.0_111\jre\lib\rt.jar;E:\BaiduNetdiskDownload\解密JVM虚拟机底层原理\代码\jvm\jvm\out\production\jvm cn.itcast.jvm.t1.stringtable.Demo1_7
0
Heap
 PSYoungGen      total 2560K, used 1954K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 95% used [0x00000000ffd00000,0x00000000ffee8988,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 7168K, used 0K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 0% used [0x00000000ff600000,0x00000000ff600000,0x00000000ffd00000)
 Metaspace       used 3454K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 382K, capacity 388K, committed 512K, reserved 1048576K
SymbolTable statistics:
Number of buckets       :     20011 =    160088 bytes, avg   8.000
Number of entries       :     14094 =    338256 bytes, avg  24.000
Number of literals      :     14094 =    600656 bytes, avg  42.618
Total footprint         :           =   1099000 bytes
Average bucket size     :     0.704
Variance of bucket size :     0.707
Std. dev. of bucket size:     0.841
Maximum bucket size     :         6
StringTable statistics:
Number of buckets       :     60013 =    480104 bytes, avg   8.000
Number of entries       :      1760 =     42240 bytes, avg  24.000
Number of literals      :      1760 =    157592 bytes, avg  89.541
Total footprint         :           =    679936 bytes
Average bucket size     :     0.029
Variance of bucket size :     0.029
Std. dev. of bucket size:     0.171
Maximum bucket size     :         2

Process finished with exit code 0
*/
  • 对内存占用情况

向常量池中放入数据

/**
 * 演示 StringTable 垃圾回收
 * -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
 */
public class Demo1_7 {
    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        try {
            for (int j = 0; j < 100000; j++) { // j=100, j=10000
                String.valueOf(j).intern();
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println(i);
        }
    }
}

/*
E:\software\jdk1.8.0_111\bin\java.exe -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc "-javaagent:E:\software\IntelliJ IDEA 2019.2.3\lib\idea_rt.jar=1874:E:\software\IntelliJ IDEA 2019.2.3\bin" -Dfile.encoding=UTF-8 -classpath E:\software\jdk1.8.0_111\jre\lib\charsets.jar;E:\software\jdk1.8.0_111\jre\lib\deploy.jar;E:\software\jdk1.8.0_111\jre\lib\ext\access-bridge-64.jar;E:\software\jdk1.8.0_111\jre\lib\ext\cldrdata.jar;E:\software\jdk1.8.0_111\jre\lib\ext\dnsns.jar;E:\software\jdk1.8.0_111\jre\lib\ext\jaccess.jar;E:\software\jdk1.8.0_111\jre\lib\ext\jfxrt.jar;E:\software\jdk1.8.0_111\jre\lib\ext\localedata.jar;E:\software\jdk1.8.0_111\jre\lib\ext\nashorn.jar;E:\software\jdk1.8.0_111\jre\lib\ext\sunec.jar;E:\software\jdk1.8.0_111\jre\lib\ext\sunjce_provider.jar;E:\software\jdk1.8.0_111\jre\lib\ext\sunmscapi.jar;E:\software\jdk1.8.0_111\jre\lib\ext\sunpkcs11.jar;E:\software\jdk1.8.0_111\jre\lib\ext\zipfs.jar;E:\software\jdk1.8.0_111\jre\lib\javaws.jar;E:\software\jdk1.8.0_111\jre\lib\jce.jar;E:\software\jdk1.8.0_111\jre\lib\jfr.jar;E:\software\jdk1.8.0_111\jre\lib\jfxswt.jar;E:\software\jdk1.8.0_111\jre\lib\jsse.jar;E:\software\jdk1.8.0_111\jre\lib\management-agent.jar;E:\software\jdk1.8.0_111\jre\lib\plugin.jar;E:\software\jdk1.8.0_111\jre\lib\resources.jar;E:\software\jdk1.8.0_111\jre\lib\rt.jar;E:\BaiduNetdiskDownload\解密JVM虚拟机底层原理\代码\jvm\jvm\out\production\jvm cn.itcast.jvm.t1.stringtable.Demo1_7
[GC (Allocation Failure) [PSYoungGen: 2048K->504K(2560K)] 2048K->696K(9728K), 0.0010827 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2552K->504K(2560K)] 2744K->760K(9728K), 0.0013222 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2552K->496K(2560K)] 2808K->808K(9728K), 0.0016740 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
100000
Heap
 PSYoungGen      total 2560K, used 1739K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 60% used [0x00000000ffd00000,0x00000000ffe36db0,0x00000000fff00000)
  from space 512K, 96% used [0x00000000fff00000,0x00000000fff7c040,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 7168K, used 312K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 4% used [0x00000000ff600000,0x00000000ff64e000,0x00000000ffd00000)
 Metaspace       used 3489K, capacity 4502K, committed 4864K, reserved 1056768K
  class space    used 387K, capacity 390K, committed 512K, reserved 1048576K
SymbolTable statistics:
Number of buckets       :     20011 =    160088 bytes, avg   8.000
Number of entries       :     14108 =    338592 bytes, avg  24.000
Number of literals      :     14108 =    601184 bytes, avg  42.613
Total footprint         :           =   1099864 bytes
Average bucket size     :     0.705
Variance of bucket size :     0.707
Std. dev. of bucket size:     0.841
Maximum bucket size     :         6
StringTable statistics:
Number of buckets       :     60013 =    480104 bytes, avg   8.000
Number of entries       :     24035 =    576840 bytes, avg  24.000
Number of literals      :     24035 =   1405128 bytes, avg  58.462
Total footprint         :           =   2462072 bytes
Average bucket size     :     0.400
Variance of bucket size :     0.404
Std. dev. of bucket size:     0.636
Maximum bucket size     :         4

Process finished with exit code 0

*/

垃圾回收

 

  • StringTable 性能调优

  1. StringTable 性能调优一
  • 正常情况下运行,60013个桶加载时间0.228秒
/**
 * 演示串池大小对性能的影响
 * -XX:+PrintStringTableStatistics
 */
public class Demo1_24 {

    public static void main(String[] args) throws IOException {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("E:\\BaiduNetdiskDownload\\解密JVM虚拟机底层原理\\代码\\jvm\\jvm\\linux.words"), "utf-8"))) {
            String line = null;
            long start = System.currentTimeMillis();
            while (true) {
                line = reader.readLine();
                if (line == null) {
                    break;
                }
                line.intern();
            }
            System.out.println("cost:" + (System.currentTimeMillis() - start) );
        }
    }
}

/*
cost:228
StringTable statistics:
Number of buckets       :     60013 =    480104 bytes, avg   8.000
Number of entries       :    481508 =  11556192 bytes, avg  24.000
Number of literals      :    481508 =  29731088 bytes, avg  61.746
Total footprint         :           =  41767384 bytes
Average bucket size     :     8.023
Variance of bucket size :     8.085
Std. dev. of bucket size:     2.843
Maximum bucket size     :        23

Process finished with exit code 0

*/
  • 减小桶的个数到1009,运行时间为3.427秒
/**
 * 演示串池大小对性能的影响
 * -Xms500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=1009
 */
public class Demo1_24 {

    public static void main(String[] args) throws IOException {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("E:\\BaiduNetdiskDownload\\解密JVM虚拟机底层原理\\代码\\jvm\\jvm\\linux.words"), "utf-8"))) {
            String line = null;
            long start = System.currentTimeMillis();
            while (true) {
                line = reader.readLine();
                if (line == null) {
                    break;
                }
                line.intern();
            }
            System.out.println("cost:" + (System.currentTimeMillis() - start) );
        }
    }
}


/*
cost:3427
StringTable statistics:
Number of buckets       :      1009 =      8072 bytes, avg   8.000
Number of entries       :    482770 =  11586480 bytes, avg  24.000
Number of literals      :    482770 =  29825720 bytes, avg  61.780
Total footprint         :           =  41420272 bytes
Average bucket size     :   478.464
Variance of bucket size :   431.735
Std. dev. of bucket size:    20.778
Maximum bucket size     :       547

Process finished with exit code 0

*/

增加桶的个数到10万,运行时间0.185秒

/**
 * 演示串池大小对性能的影响
 * -Xms500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=100000
 */
public class Demo1_24 {

    public static void main(String[] args) throws IOException {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("E:\\BaiduNetdiskDownload\\解密JVM虚拟机底层原理\\代码\\jvm\\jvm\\linux.words"), "utf-8"))) {
            String line = null;
            long start = System.currentTimeMillis();
            while (true) {
                line = reader.readLine();
                if (line == null) {
                    break;
                }
                line.intern();
            }
            System.out.println("cost:" + (System.currentTimeMillis() - start) );
        }
    }
}

/*
cost:185
StringTable statistics:
Number of buckets       :    100000 =    800000 bytes, avg   8.000
Number of entries       :    481508 =  11556192 bytes, avg  24.000
Number of literals      :    481508 =  29731088 bytes, avg  61.746
Total footprint         :           =  42087280 bytes
Average bucket size     :     4.815
Variance of bucket size :     4.838
Std. dev. of bucket size:     2.199
Maximum bucket size     :        17

Process finished with exit code 0

*/
  • 总结:增大桶的数量可以加快StringTable加载数据的速度
  • StringTable 性能调优二
public class Demo1_25 {

    public static void main(String[] args) throws IOException {

        List<String> address = new ArrayList<>();
        System.in.read();
        for (int i = 0; i < 10; i++) {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("E:\\BaiduNetdiskDownload\\解密JVM虚拟机底层原理\\代码\\jvm\\jvm\\linux.words"), "utf-8"))) {
                String line = null;
                long start = System.nanoTime();
                while (true) {
                    line = reader.readLine();
                    if(line == null) {
                        break;
                    }
                    address.add(line);
                    //address.add(line.intern());
                }
                System.out.println("cost:" +(System.nanoTime()-start)/1000000);
            }
        }
        System.in.read();
    }
}

打开 java VisualVm工具监控

 等待输入前内存使用情况,

加载数据后,字符串和char数组的内存占用最高

char[]182626720182,626,720 (52.2%)4,821,186 (49.3%)
java.lang.String115538448115,538,448 (33.0%)4,814,102 (49.2%)

  • 以为以上样本数据重读读取存在重复数据,将字符串对象加到常量池StringTable里面,减少字符串对象个数

public class Demo1_25 {

    public static void main(String[] args) throws IOException {

        List<String> address = new ArrayList<>();
        System.in.read();
        for (int i = 0; i < 10; i++) {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("E:\\BaiduNetdiskDownload\\解密JVM虚拟机底层原理\\代码\\jvm\\jvm\\linux.words"), "utf-8"))) {
                String line = null;
                long start = System.nanoTime();
                while (true) {
                    line = reader.readLine();
                    if(line == null) {
                        break;
                    }
                    //address.add(line);
                    address.add(line.intern());
                }
                System.out.println("cost:" +(System.nanoTime()-start)/1000000);
            }
        }
        System.in.read();
    }
}

char[]2057425620,574,256 (22.0%)527,005 (46.1%)
java.lang.String1257307212,573,072 (13.4%)523,878 (45.4%)

可以明显看到到有大量重复数据是采用 intern() 方法将字符串对象放入StringTable 可以减小内存占用

  • 直接内存

Direct Memory

  1. 常见于 NIO 操作时,用于数据缓冲区
  2. 分配回收成本较高,但读写性能高
  3. 不受 JVM 内存回收管理

传统内存

直接内存:少一次数据的缓存操作

/**
 * 演示 ByteBuffer 作用
 */
public class Demo1_9 {
    static final String FROM = "E:\\编程资料\\第三方教学视频\\youtube\\Getting Started with Spring Boot-sbPSjI4tt10.mp4";
    static final String TO = "E:\\a.mp4";
    static final int _1Mb = 1024 * 1024;

    public static void main(String[] args) {
        io(); // io 用时:1535.586957 1766.963399 1359.240226
        directBuffer(); // directBuffer 用时:479.295165 702.291454 562.56592
    }

    private static void directBuffer() {
        long start = System.nanoTime();
        try (FileChannel from = new FileInputStream(FROM).getChannel();
             FileChannel to = new FileOutputStream(TO).getChannel();
        ) {
            ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb);
            while (true) {
                int len = from.read(bb);
                if (len == -1) {
                    break;
                }
                bb.flip();
                to.write(bb);
                bb.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.nanoTime();
        System.out.println("directBuffer 用时:" + (end - start) / 1000_000.0);
    }

    private static void io() {
        long start = System.nanoTime();
        try (FileInputStream from = new FileInputStream(FROM);
             FileOutputStream to = new FileOutputStream(TO);
        ) {
            byte[] buf = new byte[_1Mb];
            while (true) {
                int len = from.read(buf);
                if (len == -1) {
                    break;
                }
                to.write(buf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.nanoTime();
        System.out.println("io 用时:" + (end - start) / 1000_000.0);
    }
}
  • 分配和回收原理

 

  1. 使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
  2. ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦 ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调 用 freeMemory 来释放直接内存

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Toroidals

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值