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 来释放直接内存

图解jvm--(一)jvm内存结构的更多相关文章

  1. JVM之--Java内存结构(第一篇)

    最近在和同事朋友聊天的时候,发现一个很让人思考的问题,很多人总觉得JVM将java和操作系统隔离开来,导致很多人不用熟悉操作系统,甚至不用了解JVM本身即可完全掌握Java这一门技术,其实个人的观点是 ...

  2. jvm(1):内存结构

    JVM内存结构 JVM内存的运行时数据区: 线程私有(在线程启动时创建) 程序计数器Program Counter Register 一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器, ...

  3. JVM原理及内存结构

    JVM是按照运行时数据的存储结构来划分内存结构的,JVM在运行java程序时,将它们划分成几种不同格式的数据,分别存储在不同的区域,这些数据统一称为运行时数据.运行时数据包括java程序本身的数据信息 ...

  4. JVM宏观认知&&内存结构

    JVM宏观认知 1.什么是虚拟机? 虚拟机是一种软件. 可分为系统虚拟机(仿真物理机)和程序虚拟机(执行单个计算机程序,比如JVM). 2.什么是Java虚拟机(JVM)? JVM是一种将字节码转化为 ...

  5. JVM运行时内存结构学习

    学习JVM运行模型比较重要,先看一幅图片: 运行时数据区(内存结构) :  1.方法区(Method Area)类的所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在这里定义.简单来说,所 ...

  6. JVM(二) 栈内存结构

    栈内存是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表.操作数栈.动态链接.返回出口等信息.每一个方法从调用直至执行完成的过程,就对应 ...

  7. jvm系列二内存结构

    二.内存结构 整体架构 1.程序计数器 作用 用于保存JVM中下一条所要执行的指令的地址 特点 线程私有 CPU会为每个线程分配时间片,当当前线程的时间片使用完以后,CPU就会去执行另一个线程中的代码 ...

  8. 【JVM】JVM 概述、内存结构、溢出、调优(基础结构+StringTable+Unsafe+ByteBuffer)

    什么是 JVM ? 定义 Java Virtual Machine - java 程序的运行环境(java 二进制字节码的运行环境) 好处 一次编写,到处运行 自动内存管理,垃圾回收功能 数组下标越界 ...

  9. 【高频Java面试题】简单说说JVM堆的内存结构和GC回收流程

    目录 前言 JVM堆内存结构简述 JVM堆内存结构图 堆初体验 结构详情 新生代 老年代 永久代/元空间 GC回收流程 GC回收流程图 GC回收详细流程 查看JDK自带可视化堆空间图 总结 前言 我们 ...

  10. JVM学习之 内存结构

    目录 一.引言 1.什么是JVM? 2.学习JVM有什么用 3.常见的JVM 4.学习路线 二.内存结构 1. 程序计数器 1.1 定义 1.2作用 2. 虚拟机栈 2.1定义 2.2栈内存溢出 2. ...

随机推荐

  1. php学习之始于html——div布局与css控制

    关于您的问题:xampp是一个集成的php开发环境,里面包含Apache,mysql等环境,主要充当一个服务器的角色, 其中有文件,数据,路径等,一个网站程序安装之后,都会有一个根目录,根目录下,有其 ...

  2. IDEA 下的 github 创建提交与修改

    本章假定你已经安装了 git 客户端,本文仅仅使用与 Mac 环境下,未在 Window下实验,但 IDEA 在 Window 和 Mac 下软件的使用方法是一致的. 1 配置账号 IDEA 需要配置 ...

  3. jquery发送请求的各种方法

    地址链接:https://www.cnblogs.com/java-dyb/p/10910566.html 关于向服务器传递数据的一些补充: json字符串与json对象之间的转换: JSON.par ...

  4. cf--TV Subscriptions (Hard Version)

    time limit per test:2 seconds memory limit per test:256 megabytes input:standard input output:standa ...

  5. 20 JavaScript随机&逻辑&比较&类型转换

    JavaScript 随机 Math.random(): 返回0~1之间的随机数,包括0不包括1 Math.floor():下舍入.Math.floor(2.9) = 2.Math.floor(Mat ...

  6. Ubuntu 16.04 安装ROS sudo rosdep init报错问题

    解决博文:https://blog.csdn.net/weixin_37835458/article/details/79878785 输入sudo rosdep init报错如下: Tracebac ...

  7. js图片轮换播放器

    <!DOCTYPE html> <html> <head> <title></title> <meta charset="u ...

  8. hive启动报错(整合spark)

    spark整合hive后,hive启动报错: ls: cannot access /export/servers/spark/lib/spark-assembly-*.jar: No such fil ...

  9. Linux kali安装或更新之后出现乱码

    打开终端,输入以下命令,之后重启. apt-get install ttf-wqy-zenhei

  10. Maven的安装与配置(eclipse,idea)

    Maven的安装与配置   一.需要准备的东西 1. JDK 2. Maven程序包 3. Eclipse 4. Idea 二.下载与安装 1. 前往https://maven.apache.org/ ...