JVM内存模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qknJyixz-1581434758098)(images/05.png)]

学习路线

内存结构----->垃圾回收—>字节码文件---->类加载器---->运行期的及时编译器

程序计数器

作用: 记住下一条令的执行地址;当前线程所执行字节码的行号指示器

特点:

线程私有

​ 线程 不会存在内存溢出

连接

由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的,在任何一个时间,一个处理器(对于多核处理器来说就是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能够恢复到正确的执行位置,每条线程都需要一个独立的程序技术器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为 线程私有

虚拟机栈

每个线程运行时所需要的内存,称为虚拟机栈

每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存

Java方法执行的内存模型

每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

每个方法在执行的同时都会创建一个栈帧(Stack Frame)💛 用于存储 局部变量表操作数栈,动态连接方法出口等信息。每一个方法从调用执行直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈和出栈的过程。

局部变量表存放了再编译期可知的各种基本数据类型(boolean,byte,short,int,long),对象引用(reference类型,它不等同于对象本身,可能是对象起始地址的指针,也可能是指向一个代表对象句柄或其他与此对象相关的位置)

局部变量表所需要的内存空间在编译期间分配完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行就期间不会改变局部变量表的大小

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yVOMO7Cj-1581434758100)(images/06.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BoeeNjyZ-1581434758100)(images/11.bmp)]

反编译之后查看局部变量表

  
/**
`局部变量表`所需要的内存空间在编译期间分配完成分配,==当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行就期间不会改变局部变量表的大小==
**/
LocalVariableTable:     //局部变量表
        Start  Length  Slot  Name   Signature
            0      10     0  args   [Ljava/lang/String;
            3       7     1    s1   Ljava/lang/String;
            6       4     2    s2   Ljava/lang/String;
            9       1     3    s3   Ljava/lang/String;

演示栈帧

./**
 * 演示栈帧
 */
public class Demo1_1 {
    public static void main(String[] args) throws InterruptedException {
        method1();
    }

    private static void method1() {
        method2(1, 2);
    }

    private static int method2(int a, int b) {
        int c =  a + b;
        return c;
    }
}
/

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I40to2OZ-1581434758101)(images/07.png)]

一些问题

垃圾回收是否牵扯到栈?

答:不需要,垃圾回收只回收堆内存

栈内存越大越好吗?

答:不一定,划分的大通常能进行多次递归调用

方法内局部变量是否为线程安全?

答: 不会。如果方法内局部变量没有逃离方法作用范围则为线程安全,反之。

/**
 * 局部变量的线程安全问题
 */
public class Demo1_17 {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        sb.append(4);
        sb.append(5);
        sb.append(6);
        new Thread(()->{
            m2(sb);
        }).start();
    }

    //线程安全
    public static void m1() {
        StringBuilder sb = new StringBuilder();
        sb.append(1);
        sb.append(2);
        sb.append(3);
        System.out.println(sb.toString());
    }

    //线程不安全
    public static void m2(StringBuilder sb) {
        sb.append(1);
        sb.append(2);
        sb.append(3);
        System.out.println(sb.toString());
    }
	
    //线程不安全
    public static StringBuilder m3() {
        StringBuilder sb = new StringBuilder();
        sb.append(1);
        sb.append(2);
        sb.append(3);
        return sb;
    }
}

栈内存溢出

什么情况下会导致占内存溢出

1、栈帧过多;例如递归过多

2、栈帧过大

/**
 * 演示栈内存溢出 java.lang.StackOverflowError
 * -Xss256k
 */
public class Demo1_2 {
    private static int count;

    public static void main(String[] args) {
        try {
            method1();
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println(count);
        }
    }

    private static void method1() {
        count++;
        method1();
    }
}

    java.lang.StackOverflowError
	at cn.itcast.jvm.t1.stack.Demo1_2.method1(Demo1_2.java:20)
	at cn.itcast.jvm.t1.stack.Demo1_2.method1(Demo1_2.java:21)
	at cn.itcast.jvm.t1.stack.Demo1_2.method1(Demo1_2.java:21)

本地方法栈

本地方法使用到的内存。

​ 虚拟机栈为虚拟机执行Java方法

​ 本地方法栈则为虚拟机使用到的Native方法

堆定义

Heap堆

通过new关键字,创建的对象都会使用堆内存

此内存区域的唯一目的就是存放对象实例

虚拟机规范中描述:所有对象实例以及数组都要在堆上分配

特点:

​ 它是线程共享的,堆中对象都需要考虑线程安全的问题

​ 它由垃圾回收机制,因此很多时候也被称为“GC堆”

堆内存溢出

/**
 * 演示堆内存溢出 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++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println(i);
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rXKh15ms-1581434758103)(images/08.png)]

堆内存诊断

  1. jps工具:查看当前系统中有哪些java进程
  2. jmap工具:查看堆内存占用情况
  3. jconsole工具:图形界面,多功能的和检测工具,可以连续监测(监测线程、cpu)
/**
 * 演示堆内存
 */
public class Demo1_4 {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("1...");
        Thread.sleep(30000);
        byte[] array = new byte[1024 * 1024 * 10]; // 10 Mb
        System.out.println("2...");
        Thread.sleep(20000);
        array = null;
        System.gc();
        System.out.println("3...");
        Thread.sleep(1000000L);
    }
}

使用jmap监测


使用jconsole监测

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6YN9qnXe-1581434758104)(images/09.png)]

方法区

存储类数据信息;

用于存储已被虚拟机加载的 类信息常量静态变量即时编译后的代码等数据。

特点: 线程共享

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YvkxvIWT-1581434758104)(images/10.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EW8bAZYE-1581434758105)(images/03.png)]

方法区内存溢出

JDK 1.8元空间内存溢出

package cn.itcast.jvm.t1.metaspace;

import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;

/**
 * 演示元空间内存溢出 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);
        }
    }
}

常量池

作用:给指令提供常量符号,以查表的方式找到它

就是一张表,虚拟机指令根据这张常量表找到要执行 的 类名、方法名、参数类型、字面量的信息(基本数据类型、boolean类型)

public class HelloWorld{
    //二进制字节码:
    //类基本信息、
    //常量池、
    //类方法定义包含了虚拟机指令
    public static void main(String[] args){
        System.out.println("hello world");
    }
    //javap -v HelloWorld.class  反编译
}

使用反编译获取的字节码信息

javap -v HelloWorld.class

Classfile /F:/jvm/src/cn/itcast/jvm/HelloWorld.class
Last modified 2020-1-17; size 567 bytes
MD5 checksum 8efebdac91aa496515fa1c161184e354
Compiled from "HelloWorld.java"
//以上为类的描述信息:类基本信息
    
public class cn.itcast.jvm.t5.HelloWorld//类的修饰符
SourceFile: "HelloWorld.java"  //
minor version: 0    //类的版本
major version: 52   //对应jdk1.8
flags: ACC_PUBLIC, ACC_SUPER 
    
//常量池 
    //常量池 
    //常量池 
    //常量池 
    //结合下面的
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/Prin
tStream;
#23 = Utf8               hello world
#24 = Class              #31            //  java/io/PrintStre
am
#25 = NameAndType        #32:#33        //  println:(Ljava/la
ng/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: ACC_PUBLIC
  Code:
    stack=1, locals=1, args_size=1
       0: aload_0
       1: invokespecial #1                  // Method java/la
ng/Object."<init>":()V
       4: return
    LineNumberTable:
      line 4: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       5     0  this   Lcn/itcast/jvm/t5/HelloWorl
d;

public static void main(java.lang.String[]);  //main方法
  descriptor: ([Ljava/lang/String;)V
  flags: 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;
}


运行时常量池

常量池是.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池;并把里面的符号地址变为真实地址。

运行期间的常量池;这时常量池中的数据仅仅是符号,还没有称为对象,也就是没有称为字符串对象等

字符串池(String Table)

常量池与串池关系:

运行期间的常量池;这时常量池中的数据仅仅是符号,还没有称为对象,也就是没有称为字符串对象等

​ 何时才会成为对象: ldc #2 执行完才会把"a"字符变为"a"字符串对象;==若串池中没有该对象,==会把生成的对象放入串池中 StringTable[“a”]

​ 何时才会成为对象: ldc #3 执行完才会把"b"字符变为"b"字符串对象 : ==若串池中没有该对象,==会把生成的对象放入串池中 StringTable[“a”,“b”]

Demon1:懒惰的常量池

         String s1 = "a"; // 懒惰的;用到才会把字符创建为字符串对象,r==若串池中没有该对象,==会把生成的对象放入串池中 StringTable["a"]
        String s2 = "b";
        String s3 = "ab";
		
		String s4 = new String("ab");//在堆上创建一对象

反编译结果

Code:
      stack=1, locals=4, args_size=1
         0: ldc           #2                  // String a;读取 #2位置的数据
         2: astore_1                                      //将数据存入到 1  s1 变量位置 
             
         3: ldc           #3                  // String b
         5: astore_2
             
         6: ldc           #4                  // String ab
         8: astore_3
             
         9: return
      LineNumberTable:
        line 11: 0
        line 12: 3
        line 13: 6
        line 21: 9
            
      LocalVariableTable:     //局部变量表
        Start  Length  Slot  Name   Signature
            0      10     0  args   [Ljava/lang/String;
            3       7     1    s1   Ljava/lang/String;
            6       4     2    s2   Ljava/lang/String;
            9       1     3    s3   Ljava/lang/String;



Demon2:变量的拼接原理

String s4 = s1 + s2; //结合下面的反编译,可以看出首先执行 创建一个StringBuilder对象
					//new StringBuilder.append("a").append("b").toString()   等价于  创建 newString("ab")


System.out.println(s3==s4);//false;
						//分析:s1与s2,s3在串池中;而 s4是new 出来的在堆中


反编译

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=5, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
                  
         //这里开始才对应String s4 = s1+s2;         
         9: new           #5                  // class java/lang/StringBuilder:创建StringBuilder对象
        12: dup
        13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V:构造方法
        16: aload_1							//将s1参数准备好				
        17: invokevirtual #7                  // Method java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;  stringBuilder.append(a)
        20: aload_2                         
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: astore        4   //变量存储在4
        29: return
                  
                  
      LineNumberTable:
        line 11: 0
        line 12: 3
        line 13: 6
        line 14: 9
        line 21: 29
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      30     0  args   [Ljava/lang/String;
            3      27     1    s1   Ljava/lang/String;
            6      24     2    s2   Ljava/lang/String;
            9      21     3    s3   Ljava/lang/String;
           29       1     4    s4   Ljava/lang/String;

          
 //常量池   
     //常量池  常量池  常量池 
         //常量池                            
 Constant pool:
   #1 = Methodref          #10.#29        //  java/lang/Object."<init>":()V
   #2 = String             #30            //  a
   #3 = String             #31            //  b
   #4 = String             #32            //  ab
   #5 = Class              #33            //  java/lang/StringBuilder
   #6 = Methodref          #5.#29         //  java/lang/StringBuilder."<init>":()V
   #7 = Methodref          #5.#34         //  java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilde
r;
   #8 = Methodref          #5.#35         //  java/lang/StringBuilder.toString:()Ljava/lang/String;
   #9 = Class              #36            //  cn/itcast/jvm/t1/stringtable/Demo1_22
  #10 = Class              #37            //  java/lang/Object
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               LocalVariableTable
  #16 = Utf8               this
  #17 = Utf8               Lcn/itcast/jvm/t1/stringtable/Demo1_22;
  #18 = Utf8               main
  #19 = Utf8               ([Ljava/lang/String;)V
  #20 = Utf8               args
  #21 = Utf8               [Ljava/lang/String;
  #22 = Utf8               s1
  #23 = Utf8               Ljava/lang/String;
  #24 = Utf8               s2
  #25 = Utf8               s3
  #26 = Utf8               s4
  #27 = Utf8               SourceFile
  #28 = Utf8               Demo1_22.java
  #29 = NameAndType        #11:#12        //  "<init>":()V
  #30 = Utf8               a
  #31 = Utf8               b
  #32 = Utf8               ab
  #33 = Utf8               java/lang/StringBuilder
  #34 = NameAndType        #38:#39        //  append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #35 = NameAndType        #40:#41        //  toString:()Ljava/lang/String;
  #36 = Utf8               cn/itcast/jvm/t1/stringtable/Demo1_22
  #37 = Utf8               java/lang/Object
  #38 = Utf8               append
  #39 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #40 = Utf8               toString
  #41 = Utf8               ()Ljava/lang/String;                                                                                             

Demon03:常量拼接原理

        String s5 = "a" + "b";  // javac 在编译期间的优化,已经在编译期确定为ab

字节码

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=5, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
                  
         6: ldc           #4                  // String ab  从常量池中取 ab
         8: astore_3
                  
         9: ldc           #4                  // String ab  从常量池中取ab
        11: astore        4                   //存储到 s3  s5 的变量都是串池中的值
      
        13: return
      LineNumberTable:
        line 11: 0
        line 12: 3
        line 13: 6
        line 15: 9
        line 21: 13
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      14     0  args   [Ljava/lang/String;
            3      11     1    s1   Ljava/lang/String;
            6       8     2    s2   Ljava/lang/String;
            9       5     3    s3   Ljava/lang/String;
           13       1     4    s5   Ljava/lang/String;
                                     
                               
                                     


Demon04:itern方法

 //  ["ab", "a", "b"]
    public static void main(String[] args) {

        String x = "ab";
        String s = new String("a") + new String("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 x为字符串常量 ,s为对象:
    }


反编译

stack=4, locals=4, args_size=1
         0: ldc           #2                  // String ab   /
         2: astore_1
         3: new           #3                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
        10: new           #5                  // class java/lang/String
        13: dup
        14: ldc           #6                  // String a
        16: invokespecial #7                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        19: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        22: new           #5                  // class java/lang/String
        25: dup
        26: ldc           #9                  // String b
        28: invokespecial #7                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        31: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        34: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        37: astore_2
        38: aload_2
        39: invokevirtual #11                 // Method java/lang/String.intern:()Ljava/lang/String;
        42: astore_3
        43: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
        46: aload_3
        47: aload_1
        48: if_acmpne     55
        51: iconst_1
        52: goto          56
        55: iconst_0
        56: invokevirtual #13                 // Method java/io/PrintStream.println:(Z)V
        59: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
        62: aload_2
        63: aload_1
        64: if_acmpne     71
        67: iconst_1
        68: goto          72
        71: iconst_0
        72: invokevirtual #13                 // Method java/io/PrintStream.println:(Z)V
        75: return


一道面试题

String s1 = "a";
String s2 = "b";
String s3 = "a"+"b";  //变量拼接 "ab";
String s4 = s1+s2;  //变量拼接 使用到StringBuilder生成一个新的String对象 :数据存储在堆中
String s5 = "ab"; //串池中数据
String s6 = s4.intern();  //串池中数据  ab

print(s3==s4)  //false
print(s3==s5)  //true
print(s3==s6) //true
//
/
    
String x2 = new String("c")+new String("d");//堆中对象 new String("cd")
String x1 = "cd";  //常量池中对象 
x2.intern();

x1==x2  //false
   
    
//将上卖弄的x2.intern()与String x1 = "cd";交换顺序
// 则结果为true


小结:

  1. 常量池中的字符串仅是符号,第一用到才变为对象

  2. 利用串池机制,来避免重复创建字符串对象(串池中对象只有一份)

  3. 字符串变量的拼接原理是StringBuilder

  4. 字符串常量的拼接原理是编译期优化

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

    ​ 5.1、1.8将字符串对象尝试放入串池,如果有则并不会放入,如果没有则会放入串池;会把串池对象返回

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

String Table的位置

在Jdk 1.6中 字符串池中的数据存储在 永久区

在Jdk 1.8中 字符串池中的数据存储在

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cQ4RVpg6-1581434758106)(images/03.png)]

### 测试常量池内存溢出
/**
 * 演示 StringTable 位置
 * 在jdk8下设置 -Xmx10m -XX:-UseGCOverheadLimit
 * 在jdk6下设置 -XX:MaxPermSize=10m
 */
public class Demo1_6 {

    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();
        int i = 0;
        try {
            for (int j = 0; j < 260000; j++) {
                list.add(String.valueOf(j).intern());//存入字符串;intern
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println(i);
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4CJR96Kk-1581434758107)(images/04.png)]

String Table垃圾回收

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值