jvm内存结构

1.程序计数器

1.1 定义

Program Counter Register 程序计数器(寄存器)

  • 作用,记住下一条jvm指令的执行地址
  • 特点
    • 是线程私有的
    • (唯一)不会存在内存溢出

1.2 作用

二进制字节码 jvm指令

 public int add();
Code:
0: iconst_1 // 把1压入操作数栈中
1: istore_1 //将int类型值存入局部变量1,这个局部变量1指局部变量表中的第一个数
2: iconst_2
3: istore_2
4: iload_1 //从局部变量1中装载int类型值,这里的局部变量指第一个数,即a
5: iload_2
6: iadd //执行相加
7: istore_3 //存储c
8: iload_3 //装载c
9: ireturn //返回
}

实现:

通过寄存器实现,把cup的寄存器当做程序计数器

2.虚拟机栈

2.1定义

java Virtual Machine Stacks (java 虚拟机栈)

  • 每个线程运行时所需要的内存,称为虚拟机栈
  • 每个栈有多个栈帧(Frame)组成,对应着每次方法调用时所占的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

问题:

  1. 垃圾回收是否涉及栈内存?方法调用完会自动弹出,回收内存,垃圾回收不涉及栈内存
  2. 栈内存分配越大越好?栈内存划得越大,线程数会越少,因为内存有限,栈内存是线程独享
  3. 方法内的局部变量是否线程安全? 线程栈的线程私有的,是线程安全的
    • 如果方法内局部变量,没有逃离方法的作用范围,它是线程安全的
    • 如果是局部变量引用了对象,并逃离方法的作用方法,需要考虑线程安全
/**
* 演示栈帧
*/
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;
}
}

通过上面的代码,设置断点,进行debug,可以观察method1以及method2的方法栈出入情况

2.2栈内存溢出

  • 栈帧过多,导致栈内存溢出
  • 栈帧过大,导致栈内存溢出

2.3线程运行诊断

案例1: cpu占用过多

定位

Linux 的 nohup java 命令 可以后台执行java代码

ps 命令可以 查看进程与线程的对应cpu占用情况

  • 用top定位哪个进程对cpu的占用过高
  • ps H -eo pid,tid,%cpu |grep 进程 id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
  • jstack 进程id (列出java线程)
    • 可以根据线程id找到有问题的线程,进一步定位到问题代码的源码行号(进程号需要转为16进制进行查找)

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

死锁

使用 jstack 看死锁

3.本地方法栈

本地方法栈发挥的作用与虚拟机栈的作用类似,用于native修饰的方法

4.堆

4.1 定义

Heap 堆

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

特点

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

4.2 堆内存溢出

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

下图可以调整堆内存大小

4.3 堆内存诊断

  1. jps工具

    • 查看当前系统中有哪些java进程
  2. jmap工具

    • 查看堆内存占用情况 jmap
  3. jconsole工具

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

    4.jvisualvm(推荐使用)

package cn.itcast.jvm.t1.heap;

/**
* 演示堆内存
*/
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);
}
}

打开cmd 输入 jps命令查看当前java线程

使用 jmap -heap 线程号 可以查看当前线程的堆的使用情况

在代码中的1,2,3步骤中分别执行3次,可以得到3个结果

案例:

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

    /**
    * 演示查看对象个数 堆转储 dump
    */
    public class Demo1_13 { public static void main(String[] args) throws InterruptedException {
    List<Student> students = new ArrayList<>();
    for (int i = 0; i < 200; i++) {
    students.add(new Student());
    // Student student = new Student();
    }
    Thread.sleep(1000000000L);
    }
    }
    class Student {
    private byte[] big = new byte[1024*1024];
    }

    使用 jvisualvm 命令进行诊断

    使用 堆Dunp 进行对当前线程内存进行转储快照,进而分析为什么垃圾回收后内存还很高

找出占用内存最大的一个数组,查看

找到原来时数组里面对象太多,占用了很多内存

5.方法区

5.1 定义

权威定义

5.2 组成

jdk 1.6 对方法区的实现称为永久代

jdk 1.8 对方法区的实现称为元空间

jdk1.6

jdk1.8以前,方法区是在jvm内存上面,jdk1.8以后,方法区是一个逻辑分区,在计算机本地内存上面

jdk1.8

5.3 方法区内存溢出

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 < 20000; i++, j++) {
//ClassWriter 作用是生成类的二进制字节码
ClassWriter cw = new ClassWriter(0);
//版本号,pubblic,类名,包名,父类
cw.visit(Opcodes.V1_6, 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);
}
}
}
  • 1.8以前(1.6)会导致永久代内存溢出

    演示永久代内存溢出  java.lang.OutOfMemoryError: PermGen space
    -XX:MaxPermSize=8m
  • 1.8以后会导致元空间内存溢出

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

场景:框架会产生很多运行时的类,容易导致内存溢出

  • spring

  • mybatis

    都用到cglib

5.3 运行时常量池

  • 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量等信息
  • 运行时常量池,常量池是*.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
// 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)
public class Demo1_22 { public static void main(String[] args) {
String s1 = "a"; //懒惰
String s2 = "b";
String s3 = "ab";
}

执行以下命令对代码进行反编译

javap -v Demo1_22.class

Classfile /D:/IDEAworkplace/jvm/out/production/jvm/cn/itcast/jvm/t1/stringtable/Demo1_22.class
Last modified 2020-1-30; size 534 bytes
MD5 checksum 5c4213b2f1defff2bb24bf7cbd5ff183
Compiled from "Demo1_22.java"
public class cn.itcast.jvm.t1.stringtable.Demo1_22
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
//常量池
Constant pool:
#1 = Methodref #6.#24 // java/lang/Object."<init>":()V
#2 = String #25 // a
#3 = String #26 // b
#4 = String #27 // ab
#5 = Class #28 // cn/itcast/jvm/t1/stringtable/Demo1_22
#6 = Class #29 // 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/t1/stringtable/Demo1_22;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 s1
#19 = Utf8 Ljava/lang/String;
#20 = Utf8 s2
#21 = Utf8 s3
#22 = Utf8 SourceFile
#23 = Utf8 Demo1_22.java
#24 = NameAndType #7:#8 // "<init>":()V
#25 = Utf8 a
#26 = Utf8 b
#27 = Utf8 ab
#28 = Utf8 cn/itcast/jvm/t1/stringtable/Demo1_22
#29 = Utf8 java/lang/Object
{
public cn.itcast.jvm.t1.stringtable.Demo1_22();
descriptor: ()V
flags: 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/t1/stringtable/Demo1_22; public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
//执行指令代码
Code:
stack=1, locals=4, 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
9: return
LineNumberTable:
line 11: 0
line 12: 3
line 13: 6
line 26: 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;
}
SourceFile: "Demo1_22.java"

5.4 StringTable(串池)

StringTable 是运行时常量池中的一个东西

// StringTable [ "a", "b" ,"ab" ]  hashtable 结构,不能扩容
public class Demo1_22 {
// 常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
// ldc #2 会把 a 符号变为 "a" 字符串对象
// ldc #3 会把 b 符号变为 "b" 字符串对象
// ldc #4 会把 ab 符号变为 "ab" 字符串对象 public static void main(String[] args) {
String s1 = "a"; //懒惰
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString() new String("ab")
String s5 = "a" + "b"; // javac 在编译期间的优化,结果已经在编译期确定为ab System.out.println(s3 == s5); }
} //结果
s4 不等于 s3 //s3是串池中的,s4是通过new对象生成的,其值存在堆中
s3 等于 s5 //

对于 单独的赋值,是对数据的直接到StringTable中取找,如果没有,则创建,有则直接取

对于 s4 = s1+s2 则是使用StringBuilder(),方法进行拼接,结果是一个新的对象,该对象存在堆上面

使用 javap -v 命令后编译结果如下

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=6, 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
9: new #5 // class java/lang/StringBuilder
12: dup
13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
16: aload_1
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
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
29: ldc #4 // String ab
31: astore 5
33: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
36: aload_3
37: aload 5
39: if_acmpne 46
42: iconst_1
43: goto 47
46: iconst_0
47: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
50: return

常量池内容

Constant pool:
#1 = Methodref #12.#36 // java/lang/Object."<init>":()V
#2 = String #37 // a
#3 = String #38 // b
#4 = String #39 // ab
#5 = Class #40 // java/lang/StringBuilder
#6 = Methodref #5.#36 // java/lang/StringBuilder."<init>":()V
#7 = Methodref #5.#41 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #5.#42 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Fieldref #43.#44 // java/lang/System.out:Ljava/io/PrintStream;
#10 = Methodref #45.#46 // java/io/PrintStream.println:(Z)V
#11 = Class #47 // cn/itcast/jvm/t1/stringtable/Demo1_22
#12 = Class #48 // java/lang/Object
#13 = Utf8 <init>
#14 = Utf8 ()V
#15 = Utf8 Code
#16 = Utf8 LineNumberTable
#17 = Utf8 LocalVariableTable
#18 = Utf8 this
#19 = Utf8 Lcn/itcast/jvm/t1/stringtable/Demo1_22;
#20 = Utf8 main
#21 = Utf8 ([Ljava/lang/String;)V
#22 = Utf8 args
#23 = Utf8 [Ljava/lang/String;
#24 = Utf8 s1
#25 = Utf8 Ljava/lang/String;
#26 = Utf8 s2
#27 = Utf8 s3
#28 = Utf8 s4
#29 = Utf8 s5
#30 = Utf8 StackMapTable
#31 = Class #23 // "[Ljava/lang/String;"
#32 = Class #49 // java/lang/String
#33 = Class #50 // java/io/PrintStream
#34 = Utf8 SourceFile
#35 = Utf8 Demo1_22.java
#36 = NameAndType #13:#14 // "<init>":()V
#37 = Utf8 a
#38 = Utf8 b
#39 = Utf8 ab
#40 = Utf8 java/lang/StringBuilder
#41 = NameAndType #51:#52 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#42 = NameAndType #53:#54 // toString:()Ljava/lang/String;
#43 = Class #55 // java/lang/System
#44 = NameAndType #56:#57 // out:Ljava/io/PrintStream;
#45 = Class #50 // java/io/PrintStream
#46 = NameAndType #58:#59 // println:(Z)V
#47 = Utf8 cn/itcast/jvm/t1/stringtable/Demo1_22
#48 = Utf8 java/lang/Object
#49 = Utf8 java/lang/String
#50 = Utf8 java/io/PrintStream
#51 = Utf8 append
#52 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#53 = Utf8 toString
#54 = Utf8 ()Ljava/lang/String;
#55 = Utf8 java/lang/System
#56 = Utf8 out
#57 = Utf8 Ljava/io/PrintStream;
#58 = Utf8 println
#59 = Utf8 (Z)V

5.5 StringTable特性

  • 常量池中的字符串仅是符号,第一次用到时才变为对象
  • hashtable 结构,不能扩容
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是 StringBuilder (1.8)
  • 字符串常量拼接的原理是编译期优化
  • 可以使用itern方法,主动将串池中还没有的字符串对象放入串池
    • 1.8 将这个字符对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回
    • 1.6 将这个字符对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池,会把串池中的对象返回

先看几道面试题:

String s1 = "a";
String s2 = "b";
String s3 = "a" + "b"; //ab
Strin s4 = s1 + s2; //new String("ab")
String s5 = "ab";
String s6 = s4.intern();//常量池中以及有"ab"了,所以s4没能入池成功 //问
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();//intern 方法在jdk1.6之前是不一样的 //问,如果调换了【最后两行代码】的位置呢?如果jdk1.6呢?
System.out.println(x1 == x2);//false

常量池与串池的关系:

常量池一开始是存在于字节码中,当运行时,都会加载到运行时常量池中,常量池中的信息都会被加载到运行时常量池,此时常量池中的符号还不是java的字符串对象

StringTable 的字符串是延迟加载机制,即要使用才加载进来,

常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象

ldc #2 会把 a 符号变为 "a" 字符串对象

ldc #3 会把 b 符号变为 "b" 字符串对象

ldc #4 会把 ab 符号变为 "ab" 字符串对象

// StringTable [ "a", "b" ,"ab" ]  hashtable 结构,不能扩容
public class Demo1_22 {
// 常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
// ldc #2 会把 a 符号变为 "a" 字符串对象
// ldc #3 会把 b 符号变为 "b" 字符串对象
// ldc #4 会把 ab 符号变为 "ab" 字符串对象 public static void main(String[] args) {
String s1 = "a"; //懒惰
String s2 = "b";
String s3 = "ab";
} //对应jvm指令为
Code:
stack=1, locals=4, 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
9: return
LineNumberTable:
line 11: 0
line 12: 3
line 13: 6
line 26: 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;
}

从指令中可以看出 String s4 = s1 + s2,是将s1以及s2的值使用StringBuilder进行拼接,最后调用StringBuilder的toString方法进行重新生成一个新的对象( 等于new StringBuilder().append("a").append("b").toString() new String("ab"))的底层是通过StringBuilder进行字符串的拼接生成字符串对象,存储在堆内存中 ,所以s4 不等于 s3

String s4 = s1 + s2;
//这一行代码的jvm指令为
9: new #5 // class java/lang/StringBuilder
12: dup
13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
16: aload_1
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
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 //查看StringBuilder 的 toString方法,看出来是生成一个新的对象
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}

String s5 = "a" + "b"; (javac在编译初期的优化,就已经确定为ab了,所以jvm指令直接读取ab,并生成对象)所以 s5 等于 s3

 String s5 = "a" + "b";
// javac 在编译期间的优化,结果已经在编译期确定为ab
//这一行代码的jvm指令为
29: ldc #4 // String ab
31: astore 5

StringTable 字符串延迟加载

在代码中设置多个断点,观察StringTable的字符数量变化,可以看出字符串是具有延迟加载机制的

在idea中debug模式中的Memory中可以查看串池中字符的个数

intern方法jdk 1.6 与 1.8区别

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

intern :

最初为空的字符串池由StringString

当调用intern方法时,如果池已经包含与equals(Object)方法确定的相当于此String对象的字符串,则返回来自池的字符串。 否则,此String对象将添加到池中,并返回对此String对象的引用。

由此可见,对于任何两个字符串sts.intern() == t.intern()true当且仅当s.equals(t)true

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

1.8

    // ["a", "b", "ab"]
public static void main(String[] args) { String s = new String("a") + new String("b"); //new String("ab") // 堆 new String("a") new String("b") new String("ab")
String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回 String x = "ab";
System.out.println(s2 == x);
System.out.println(s == x);
}
} //输出
//true
//true
    // ["ab", "a", "b"]
public static void main(String[] args) { String x = "ab";
String s = new String("a") + new String("b"); //new String("ab") // 堆 new String("a") new String("b") new String("ab")
String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
//由于此时,"ab"已经存在,直接返回"ab",s没能入池成功 System.out.println( s2 == x);
System.out.println( s == x ); //此时 s 是在堆中的对象
} }
//输出
//true
//false

jdk1.6

    // ["ab", "a", "b"]
public static void main(String[] args) { String x = "ab";
String s = new String("a") + new String("b"); //new String("ab") // 堆 new String("a") new String("b") new String("ab")
String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
// s 拷贝一份,放入串池 // System.out.println(s2 == "ab");
// System.out.println(s == "ab"); System.out.println( s2 == x);
System.out.println( s == x );
} } //输出
//true
//false
    // [ "a", "b","ab"]
public static void main(String[] args) { String s = new String("a") + new String("b"); //new String("ab") // 堆 new String("a") new String("b") new String("ab")
String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
// s 拷贝一份,放入串池 // System.out.println(s2 == "ab");
// System.out.println(s == "ab"); String x = "ab";
System.out.println( s2 == x);
System.out.println( s == x ); //s依然是堆里面的值,不一样
} } //输出
//true
//false

5.6 StringTable位置

  1. 7 的时候StringTable 是在堆空间

1.6 时StringTable是在永久代中 ,永久代是Full GC (老年代空间不足才会触发)才会触发,导致StringTable的回收效率不高,所以在1.7 以后把StringTable 转移到堆中(只需要miner GC 就能触发垃圾回收)

/**
* 演示 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());//把产生的数字变成字符,然后加入串池中
i++;
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
System.out.println(i);
}
}
}

jdk 1.8

java.lang.OutOfMemoryError: Java heap space

堆空间不足(StringTable 在堆中)

jdk 1.6

永久代空间不足(StringTable在永久代中)

java.lang.OutOfMemoryError: PermGen space

5.7 StringTable 垃圾回收

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

5.8 StringTable 性能调优

  • 调整 -XX:StringTableSize=桶个数
  • 考虑将字符串对象是否入池

​ 如果字符常量比较多时,可以把桶的个数(StringTableSize)调大,让有更多的hash,减少hash冲突

/**
* 演示 intern 减少内存占用
* -XX:StringTableSize=200000 -XX:+PrintStringTableStatistics
* -Xsx500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=200000
*/ 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("linux.words"), "utf-8"))) {
String line = null;
long start = System.nanoTime();
while (true) {
line = reader.readLine();
if(line == null) {
break;
}
address.add(line.intern());
}
System.out.println("cost:" +(System.nanoTime()-start)/1000000);
}
}
System.in.read(); }
}

6.直接内存

6.1 定义

  • 常见于NIO操作时,用于数据缓冲区

  • 分配回收成本较高,但读写性能高

  • 不受JVM内存回收管理

    传统io

ByteBuffer:

/**
* 演示 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);
}
}

6.2 分配和回收原理

  • 使用了Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用freeMemory方法

  • ByteBuffer 的实现类内部,使用了 Cleaner(虚引用) 来监测 ByteBuffer 对象,一旦 ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMenory 来释放直接内存

最新文章

  1. java多线程--定时器Timer的使用
  2. 快速部署tomcat项目的Shell脚本
  3. [转]EL表达式和JSTL表达式实例
  4. 微信小程序--摸索之旅
  5. 设置root用户不保存终端历史记录到.bash_history
  6. 【转】OpenStack奥斯汀峰会Keynotes国内抢先看
  7. 通过配置的方式Autofac 《第三篇》
  8. LazyLoad使用注意
  9. Vijos p1518 河流 转二叉树左儿子又兄弟
  10. u盘安装ubuntu server 14.04 以及No CD-ROM drive was detected 错误
  11. sqlplus 远程oracle
  12. WordPress BackWPup插件‘tab’参数跨站脚本漏洞
  13. 《asp.net mvc3 高级编程》第一章
  14. android 程序中res/values-v14/styles.xml报错的解决办法
  15. phpadmin
  16. 【Beta】Daily Scrum Meeting——Day1
  17. 数组的创建和各种API
  18. 【English】20190320
  19. day26 第二阶段共享
  20. Wannafly挑战赛26-F-msc的棋盘[最小割转化dp]

热门文章

  1. python opencv:保存图像
  2. Spring Boot Ftp Client 客户端示例支持断点续传
  3. Doc-Compose
  4. idea 配置JVM参数
  5. tf.app.run()的作用
  6. DBC的故事(二)
  7. Redis常用命令操作
  8. python中:from * import 与 import 详解
  9. Java通过反射实现实例化
  10. 六 Struts2访问Servlet的API方式一:完全解耦合的方式