首页 技术 正文
技术 2022年11月9日
0 收藏 435 点赞 3,026 浏览 15729 个字

什么是 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);
}
}
相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,497
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,910
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,744
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,498
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:8,137
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:5,301