JVM
目录
什么是 JVM ?
定义: Java Virtual Machine - java 程序的运行环境(java 二进制字节码的运行环境)
好处:
- 一次编写,跨平台运行
- 自动内存管理,垃圾回收功能
- 数组下标越界检查
- 多态
比较: jvm jre jdk
-
常见的 JVM
-
学习路线
-
JVM内存结构
-
1. 程序计数器
- 定义 Program Counter Register 程序计数器(寄存器)
- 作用,是记住下一条jvm指令的执行地址
- 特点 每个线程都有自己的程序计数器,是线程私有的 不会存在内存溢出
-
2. 虚拟机栈
- Java Virtual Machine Stacks (Java 虚拟机栈)
- 定义
- 每个线程运行时所需要的内存,称为虚拟机栈
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
- 活动栈帧是每个栈顶端的栈帧
-
问题辨析
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
-
栈内存溢出
- 栈帧过多导致栈内存溢出:常见的有方法的递归调用可能会导致栈帧过多而栈内存溢出
- 栈帧过大导致栈内存溢出: 常见的有生成集合时循环引用(例如:生成json时存入学生对象,属性课程中也引用学生对象这样循环引用)
-
线程运行诊断
- 定位
- 用top定位哪个进程对cpu的占用过高
- ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
- jstack 进程id 可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号
- 案例2:程序运行很长时间没有结果
死锁:互相等待
-
本地方法栈
jvm调用本地方法时需要的内存空间(本地方法是指不是由java代码编写的一些方法,Object类中有很多本地方法:clone()、hashCode()、notify()、notifyAll()、wait()等方法)
-
堆
- 定义 Heap 堆
- 通过 new 关键字,创建对象都会使用堆内存
特点
- 它是线程共享的,堆中对象都需要考虑线程安全的问题
- 有垃圾回收机制
-
堆内存溢出
一直创建对象就会造成堆内存溢出
- 堆内存诊断
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虚拟机实现可以为程序员或用户提供对方法区域初始大小的控制,以及在方法区域大小可变的情况下,可以控制最大和最小方法区域大小。
-
方法区内存溢出
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>
-
运行时常量池
- 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量 等信息
- 运行时常量池,常量池是 *.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 性能调优
- 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[] | 182626720 | 182,626,720 (52.2%) | 4,821,186 (49.3%) |
java.lang.String | 115538448 | 115,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[] | 20574256 | 20,574,256 (22.0%) | 527,005 (46.1%) |
java.lang.String | 12573072 | 12,573,072 (13.4%) | 523,878 (45.4%) |
可以明显看到到有大量重复数据是采用 intern() 方法将字符串对象放入StringTable 可以减小内存占用
-
直接内存
Direct Memory
- 常见于 NIO 操作时,用于数据缓冲区
- 分配回收成本较高,但读写性能高
- 不受 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);
}
}
-
分配和回收原理
- 使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
- ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦 ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调 用 freeMemory 来释放直接内存