什么是 JVM ?

定义

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

好处

  • 一次编写,到处运行

  • 自动内存管理,垃圾回收功能

  • 数组下标越界检查

  • 多态

  • jvm jre jdk

常见的 JVM

整体结构

内存结构

程序计数器

定义

  • Program Counter Register 程序计数器(寄存器)
  • 作用
    • 是记住下一条 jvm 指令的执行地址,也就是线程当前要执行的指令地址
  • 特点
    • 线程私有
    • 不会存在内存溢出(唯一)

虚拟机栈

定义

  • Java Virtual Machine Stacks (Java 虚拟机栈)
  • 每个线程运行时所需要的内存,称为虚拟机栈
  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
  • 栈的大小
    • Linux/x64(64-bit):1024 KB
    • maxOS(64-bit):1024 KB
    • Oracle Solaris/x64(64-bit):1024 KB
    • Windows:The default value depends on virtual memory

问题

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

    不涉及。每一次方法调用之后栈帧会被弹出,释放内存,不需要垃圾回收。

  • 栈内存分配越大越好吗?

    不。计算机总的物理内存有限,栈内存越大,栈的数量就越少,能够开启的线程就越少

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

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

栈内存溢出

  • 栈帧过多导致栈内存溢出
  • 栈帧过大导致栈内存溢出
    public static void main(String[] args) throws Exception{
try {
method();
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(count);
}
} public static void method() {
count++;
method();
}
21187
Exception in thread "main" java.lang.StackOverflowError

本地方法栈

定义

  • 管理本地方法,即非 Java 语言编写的方法(C语言)的调用

  • Navtive 方法是 Java 通过 JNI 直接调用本地 C/C++ 库

  • 线程私有

  • HotSpot 虚拟机直接把本地方法栈和虚拟机栈合二为一

// Object 类中有大量的本地方法

    public final native Class<?> getClass();

    public native int hashCode();

    protected native Object clone() throws CloneNotSupportedException;

    public final native void notify();

    public final native void notifyAll();

    public final native void wait(long timeout) throws InterruptedException;

定义

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

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

  • 垃圾回收机制

堆内存溢出

  • 创建的对象被虚拟机认为有用,不被回收,最后可能造成 OOM

  • 注意不一定非得 new 对象的时候才会出现。

    public static void main(String[] args) throws Exception {
String s = "a";
ArrayList<String> array = new ArrayList<>();
int count = 0;
try {
while (true) {
s += "a";
array.add(s);
count++;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(count);
}
}
60311
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

堆内存诊断

  • jps 工具

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

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

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

    更强大的可视化工具

实例:

  • 输出 1... 之后,线程休眠 30 秒
  • 终端输入 jps,查看进程 id,寻找到 Main 线程的 pid
  • 终端输入 jmap -heap pid
  • 程序创建一个 10 MB 大小的 byte 数组,
  • 输出 2... 之后,线程休眠 30 秒
  • 终端输入 jmap -heap pid
  • 垃圾回收,释放数组内存
  • 输出 3... 之后,线程休眠
  • 终端输入 jmap -heap pid
    public static void main(String[] args) throws Exception {
System.out.println("1...");
Thread.sleep(30000);
byte[] bytes = new byte[1024 * 1024 * 10];
System.out.println("2...");
Thread.sleep(30000);
bytes = null;
System.gc();
System.out.println("3...");
Thread.sleep(1000000L);
}

三次输入 jmap -heap pid 之后输出的部分内容如下

1️⃣ 第一次:程序刚开始

Eden Space:
capacity = 66584576 (63.5MB)
used = 7990344 (7.620185852050781MB)
free = 58594232 (55.87981414794922MB)
12.000292680394931% used

2️⃣ 第二次:创建 10 MB byte 数组之后

Eden Space:
capacity = 66584576 (63.5MB)
used = 18476120 (17.620201110839844MB)
free = 48108456 (45.879798889160156MB)
27.748348206046998% used

注意到 used 大小扩大了 10 MB

3️⃣ 第三次:垃圾回收之后

Eden Space:
capacity = 66584576 (63.5MB)
used = 1331712 (1.27001953125MB)
free = 65252864 (62.22998046875MB)
2.0000307578740157% used

发现 used 减小明显。

还可以使用 jconsole 图形化工具

程序运行之后终端输入 jconsole 即可

使用 jvisualvm 获取更详细的堆内存描述:

jvisualvm  // 终端输入

使用 堆 Dump 可以查看堆内具体信息。

方法区

定义

  • 方法区(method area)只是 JVM 规范中定义的一个概念,用于存储类信息、常量池、静态变量、JIT编译后的代码等数据,不同的实现可以放在不同的地方。

  • 逻辑上是堆的一部分,但不同厂商具体实现起来是不一样的,不强制位置

  • hotspot 虚拟机使得在 jdk1.8 之前方法区由永久代实现,在jdk1.8之后由元空间实现(本地内存)

  • 线程共享

  • 会导致内存溢出

方法区内存溢出

  • jdk1.8 元空间内存溢出

因为虚拟机默认使用本机内存作为元空间,内存较大,所以要调小一下元空间的大小。

输入参数

-XX:MaxMetaspaceSize=10m
public class Test extends ClassLoader {
public static void main(String[] args) {
int j = 0;
try {
Test test = new Test();
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 对象
}
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(j);
}
}
}
3331
Exception in thread "main" java.lang.OutOfMemoryError: Compressed class space

和预想的不太一样,Compressed class space 是什么呢?

在 64 位平台上,HotSpot 使用了两个压缩优化技术,Compressed Object Pointers (“CompressedOops”) 和 Compressed Class Pointers。

压缩指针,指的是在 64 位的机器上,使用 32 位的指针来访问数据(堆中的对象或 Metaspace 中的元数据)的一种方式。

这样有很多的好处,比如 32 位的指针占用更小的内存,可以更好地使用缓存,在有些平台,还可以使用到更多的寄存器。

-XX:+UseCompressedOops 允许对象指针压缩。

-XX:+UseCompressedClassPointers 允许类指针压缩。

它们默认都是开启的,可以手动关闭它们。

VM options中输入

-XX:-UseCompressedOops
-XX:-UseCompressedClassPointers

再次运行结果如下

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

表明元空间内存溢出。

  • jdk1.6 永久代内存溢出

相同的代码和虚拟机参数配置,结果如下

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

表明永久代内存溢出

运行时常量池

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

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

反编译字节码命令(终端先 cd 进入 out 目录下相应字节码文件的目录)

javap -v Class.class
  • 二进制字节码:由类基本信息、常量池、类方法定义、虚拟机指令组成
public class test02 {
public static void main(String[] args) {
System.out.println("hello world");
}
}
E:\Project\JavaProject\Practice\out\production\Practice\demo04>javap -v test02.class
Classfile /E:/Project/JavaProject/Practice/out/production/Practice/demo04/test02.class
Last modified 2021-11-18; size 535 bytes
MD5 checksum 6da0b7066cec4b7beb4be01700bf3897
Compiled from "test02.java"
public class demo04.test02
minor version: 0
major version: 52
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 // demo04/test02
#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 Ldemo04/test02;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 SourceFile
#19 = Utf8 test02.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 demo04/test02
#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 demo04.test02(); // 构造方法
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 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ldemo04/test02; public static void main(java.lang.String[]);
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 5: 0
line 6: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
}
SourceFile: "test02.java"
  • 常量池可以给虚拟机指令提供一些常量符号,可以通过查表的方式查到。

StringTable

StringTable 的数据结构

  • hash表(数组+链表)
  • 不可扩容
  • 存字符串常量,唯一不重复
  • 每个数组单元称为一个哈希桶
  • 大小至少是 1009

面试题

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);
System.out.println(s3 == s5);
System.out.println(s3 == s6);
String x2 = new String("c") + new String("d");
String x1 = "cd";
x2.intern();
// 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
// x2.intern();
// String x1 = "cd";
System.out.println(x1 == x2);
false
true
true
false
// 调换后,true

解析

  • 常量池中的字符串仅是符号,第一次用到时才变为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是 StringBuilder (1.8)
  • 字符串常量拼接的原理是编译期优化
  • 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
  • jdk1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
  • jdk1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回
字符串常量
    String s1 = "a";
String s2 = "b";
String s3 = "ab";

反编译后的执行过程:

    Constant pool:
#1 = Methodref #6.#24 // java/lang/Object."<init>":()V
#2 = String #25 // a
#3 = String #26 // b
#4 = String #27 // ab
... 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
...
    常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
ldc #2 会把 a 符号变为 "a" 字符串对象
ldc #3 会把 b 符号变为 "b" 字符串对象
ldc #4 会把 ab 符号变为 "ab" 字符串对象

字符串延迟加载

字符串变量拼接
    String s1 = "a"; // 懒惰的
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;

反编译结果

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
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: return

字符串拼接的过程 new StringBilder().append("a").append("b").toString(),而StringBuildertoString()方法又在底层创建了一个String对象

    @Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}

所以 s3 == s4false

字符串常量拼接
    String s1 = "a"; // 懒惰的
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
String s5 = "a" + "b";

反编译结果

    Code:
stack=2, 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: return

注意 29: ldc #4 // String ab6: ldc #4 // String ab

指向的是字符串常量池中相同的字符串常量 #4,说明 javac 在编译期间进行了优化,结果已经在编译期确定为 ab

所以 s3 == s5true

intern 方法
        String s = new String("a") + new String("b");

反编译结果

Constant pool:
...
#5 = String #30 // a
...
#8 = String #33 // b
...
Code:
stack=4, locals=2, args_size=1
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: new #4 // class java/lang/String
10: dup
11: ldc #5 // String a
13: invokespecial #6 // Method java/lang/String."<init>":(Ljava/lang/String;)V
16: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: new #4 // class java/lang/String
22: dup
23: ldc #8 // String b
25: invokespecial #6 // Method java/lang/String."<init>":(Ljava/lang/String;)V
28: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: astore_1
35: return
...

可以发现,创建了三个对象,"a","b" 以及StringBuilder.toString()创建的 "ab"

字符串常量 "a","b" 进入串池,"ab" 是动态拼接出的一个字符串,没有被放入串池。

s 是一个变量指向堆中的 "ab" 字符串对象

调用 String.intern() 方法可以将这个字符串对象尝试放入串池,如果有则并不会放入,把串池中的对象返回;如果没有则放入串池, 再把串池中的对象返回。

注意这里说的返回是指调用 String.intern() 方法后返回的值。比如 String ss = s.intern() , ss 接收返回的对象,与 s 无关。而 s 只与对象本身有关,与返回值无关。

        String x = "ab";
String s = new String("a") + new String("b");
String s2 = s.intern(); System.out.println(s2 == x);
System.out.println(s == x);

过程:

  • 字符串常量 "ab" 放入串池
  • "a""b" 放入串池
  • s 指向堆中创建的 "ab" 对象
  • 串池中已经有 "ab" 对象,则返回串池中的对象引用给变量 s2s 依然指向堆中的 "ab" 对象
  • s2 == xtrue
  • s == xfalse

如果调换一下位置

        String s = new String("a") + new String("b");
String s2 = s.intern();
String x = "ab"; System.out.println( s2 == x);
System.out.println( s == x );

过程:

  • "a""b" 放入串池
  • s 指向堆中创建的 "ab" 对象
  • 串池中没有 "ab" 对象,则返回串池中的对象引用给变量 s2s 指向串池中的 "ab" 对象
  • s2 == xtrue
  • s == xtrue

StringTable 的位置

  • jdk1.6 StringTable 放在永久代中,与常量池放在一起

  • jdk1.8 StringTable 放在堆中

StringTable 垃圾回收

  • StringTable 会发生垃圾回收
-Xmx10m -XX:+PrintStringTableStatistics
-XX:+PrintGCDetails -verbose:gc
    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);
}
}
[GC (Allocation Failure) [PSYoungGen: 2048K->488K(2560K)] 2048K->676K(9728K), 0.0010489 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
...
StringTable statistics:
Number of buckets : 60013 = 480104 bytes, avg 8.000
Number of entries : 4388 = 105312 bytes, avg 24.000
Number of literals : 4388 = 284264 bytes, avg 64.782
Total footprint : = 869680 bytes

可以看到 entries 的个数小于 10000,从第一行也可以看出发生了 GC

StringTable 调优

调整 StringTable 的大小
  -XX:StringTableSize=桶个数
  • 哈希桶越多,分布越分散,发生哈希冲突的可能性越低,效率越高

  • 字符串常量多的话,可以调大 StringTable 的大小,能增加哈希桶的个数,提供效率

考虑字符串是否入池
  • 使用 String.intern() 方法使重复字符串常量入池,减少堆的内存占用
    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();
}

直接内存

定义

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

Java 本身不具有磁盘读写能力,需要调用操作系统提供的函数。

当 CPU 从用户态切换为内核态时,操作系统中会划分出一个系统缓冲区,Java 无法直接访问系统缓冲区,而堆中存在 Java 缓冲区,数据进入系统缓冲区再进入 Java 缓冲区就可以被 Java 访问。

两个缓冲区直接存在不必要的数据复制。

直接内存可以使系统缓冲区和 Java 缓冲区共享,使 Java 可以直接访问系统缓冲区的数据,减少了不必要的数据复制,适合文件的 IO 操作。

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);
}
}

分配和回收原理

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

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

ByteBuffer 的 allocateDirect 方法

    public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}

DirectByteBuffer 对象

    // Primary constructor
//
DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap); long base = 0;
try {
base = unsafe.allocateMemory(size); // 调用了 unsafe 类的 allocateMemory 方法
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); // Cleaner 虚引用监控 DirectByteBuffer 对象
att = null;
}

Cleanr 对象的 clean 方法

 public void clean() {
if (remove(this)) {
try {
this.thunk.run(); // 执行任务对象
} catch (final Throwable var2) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (System.err != null) {
(new Error("Cleaner terminated abnormally", var2)).printStackTrace();
} System.exit(1);
return null;
}
});
} }
}

Deallocator 任务对象

  private static class Deallocator
implements Runnable
{ private static Unsafe unsafe = Unsafe.getUnsafe(); private long address;
private long size;
private int capacity; private Deallocator(long address, long size, int capacity) {
assert (address != 0);
this.address = address;
this.size = size;
this.capacity = capacity;
} public void run() {
if (address == 0) {
// Paranoia
return;
}
unsafe.freeMemory(address);
address = 0;
Bits.unreserveMemory(size, capacity);
} }

DirectByteBuffer 这个 Java 对象被垃圾回收器调用的时候,会触发虚引用对象 Cleaner 中的 clean 方法,执行任务对象 Deallocator,调用任务对象中的 freeMemory 去释放直接内存。

禁用显式垃圾回收

禁用显式垃圾回收

  -XX:+DisableExplicitGC // 禁用显式的 System.gc()

System.gc() 触发的是 Full GC,回收新生代和老年代,程序暂停时间长,JVM 调优的时候可能会禁用掉,防止无意使用 System.gc()

但是禁用显式的 System.gc() ,直接内存不能被即时释放,可以通过直接调用 UnsafefreeMemory 方法手动管理回收直接内存。

    static int _1Gb = 1024 * 1024 * 1024;

    public static void main(String[] args) throws IOException {
Unsafe unsafe = getUnsafe();
// 分配内存
long base = unsafe.allocateMemory(_1Gb);
unsafe.setMemory(base, _1Gb, (byte) 0);
System.in.read(); // 释放内存
unsafe.freeMemory(base);
System.in.read();
} public static Unsafe getUnsafe() {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
return unsafe;
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}

【JVM】JVM 概述、内存结构、溢出、调优(基础结构+StringTable+Unsafe+ByteBuffer)的更多相关文章

  1. <JVM下篇:性能监控与调优篇>补充:浅堆深堆与内存泄露

    笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机) 同步更新:https://gitee.com/vectorx/NOTE_JVM https://codechina.cs ...

  2. jvm系列二内存结构

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

  3. <JVM下篇:性能监控与调优篇>01-概述篇-02-JVM监控及诊断工具-命令行篇

    笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机) 同步更新:https://gitee.com/vectorx/NOTE_JVM https://codechina.cs ...

  4. <JVM下篇:性能监控与调优篇>03-JVM监控及诊断工具-GUI篇

    笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机) 同步更新:https://gitee.com/vectorx/NOTE_JVM https://codechina.cs ...

  5. <JVM下篇:性能监控与调优篇>补充:使用OQL语言查询对象信息

    笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机) 同步更新:https://gitee.com/vectorx/NOTE_JVM https://codechina.cs ...

  6. JVM垃圾回收机制总结:调优方法

    转载: JVM垃圾回收机制总结:调优方法 JVM 优化经验总结 JVM 垃圾回收器工作原理及使用实例介绍

  7. Linux 系统TCP连接内存大小限制 调优

    系统TCP连接内存大小限制 TCP的每一个连接请求,读写都需要占用系统内存资源,可根据系统配置,对TCP连接数,内存大小,限制调优. 查看系统内存资源 记录内存 详情:cat /proc/meminf ...

  8. 鲲鹏性能优化十板斧(二)——CPU与内存子系统性能调优

    1.1 CPU与内存子系统性能调优简介 调优思路 性能优化的思路如下: l   如果CPU的利用率不高,说明资源没有充分利用,可以通过工具(如strace)查看应用程序阻塞在哪里,一般为磁盘,网络或应 ...

  9. JVM(五) 生产环境内存溢出调优

    1.gc配置参数 1.1 控制台打印gc日志 -verbose:gc -XX:+PrintGCDetails -XX:+PrintHeapAtGC(详细的gc信息) 1.2 输出gc日志到指定文件 - ...

随机推荐

  1. c++ class里面成员和分配内存问题

    慢慢开始学c++啦,记录学习的大体过程 class中神奇的内存(sizeof) 1.内存补齐 便于管理类(生成的对象)的内存,类总内存总是为最大成员字节大小的倍数,不足的会进行内存补齐 类的整体内存就 ...

  2. NOI 2016 Day1 题解

    今天写了NOI2016Day1的题,来写一发题解. T2 网格 题目传送门 Description \(T\) 次询问,每次给出一个 \(n\times m\) 的传送门,上面有 \(c\) 个位置是 ...

  3. LOJ6469 Magic(trie)

    纪念我菜的真实的一场模拟赛 首先看到这个题目,一开始就很毒瘤.一定是没有办法直接做的. 我们考虑转化问题 假设,我们选择枚举\(x\),其中\(x\)是\(10\)的若干次方,那么我们只需要求有多少对 ...

  4. 封装ARX给.Net调用

    1:创建工程名.def的文件,内容如下: 2:def文件位置: 3:属性页配置: 4:acrxEntryPoint.cpp下面添加如下代码(可以传参数) 5:c#调用 怕自己忘记,记录一下.

  5. 在Excel中,不利用任何第三方工具,生成二维码

    有同事提需求,要批量生成二维码.谈了之后,我觉得可以做个excel文件,把要打印的内容放进去,然后给每行数据生成一个二维码.下一步就要在Excel里面生成二维码.问了一下度娘,貌似都得利用一些第三方工 ...

  6. [Beta]the Agiles Scrum Meeting 12

    会议时间:2020.5.27 21:00 1.每个人的工作 今天已完成的工作 成员 已完成的工作 issue yjy 帮助解决技术问题 tq 撰写技术博客 wjx 博客评分界面美化 dzx 博客评分界 ...

  7. 【技术博客】Flutter—使用网络请求的页面搭建流程、State生命周期、一些组件的应用

    Flutter-使用网络请求的页面搭建流程.State生命周期.一些组件的应用 使用网络请求的页面搭建流程 ​ 在开发APP时,我们常常会遇到如下场景:进入一个页面后,要先进行网络调用,然后使用调用返 ...

  8. UltraSoft - Beta - Scrum Meeting 3

    20200519会议纪要 Date: May 19th, 2020. Scrum 情况汇报 进度情况 组员 负责 今日进度 q2l PM.后端 暂无 Liuzh 前端 暂无 Kkkk 前端 完成了前端 ...

  9. oo第三单元学习总结

    OO第三单元小结 一.JML语言理论基础及工具链梳理 在本单元我们学习了JML语言的一些基础知识,能够让我们看懂简单的JML规格并写出对应代码, 主要用到的知识点有:   1.requires 该子句 ...

  10. springboot多配置环境

    在我们的开发过程中,经常会有多套配置环境,比如开发环境(dev),测试环境(test),生产环境(prod)等,在各个环境中我们需要使用到不同的配置,那么在springboot中是如何做到的呢? 1. ...