农业资讯网
当前位置: 首页 农业百科

jvm内存溢出服务就挂了吗(VM概述内存结构)

时间:2023-06-03 作者: 小编 阅读量: 1 栏目名: 农业百科

VM概述内存结构什么是JVM?定义JavaVirtualMachine-java程序的运行环境(java二进制字节码的运行环境)好处一次编写,到处运行自动内存管理,垃圾回收功能数组下标越界检查多态jvmjrejdk常。

什么是 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();}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);}}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=10mpublic 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);}}}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

再次运行结果如下

9344Exception 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");}}:\Project\JavaProject\Practice\out\production\Practice\demo04>javap -v test02.classClassfile /E:/Project/JavaProject/Practice/out/production/Practice/demo04/test02.classLast modified 2021-11-18; size 535 bytesMD5 checksum 6da0b7066cec4b7beb4be01700bf3897Compiled from "test02.java"public class demo04.test02minor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPERConstant 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 = Utf8Code#10 = Utf8LineNumberTable#11 = Utf8LocalVariableTable#12 = Utf8this#13 = Utf8Ldemo04/test02;#14 = Utf8main#15 = Utf8([Ljava/lang/String;)V#16 = Utf8args#17 = Utf8[Ljava/lang/String;#18 = Utf8SourceFile#19 = Utf8test02.java#20 = NameAndType#7:#8// "<init>":()V#21 = Class#28// java/lang/System#22 = NameAndType#29:#30// out:Ljava/io/PrintStream;#23 = Utf8hello world#24 = Class#31// java/io/PrintStream#25 = NameAndType#32:#33// println:(Ljava/lang/String;)V#26 = Utf8demo04/test02#27 = Utf8java/lang/Object#28 = Utf8java/lang/System#29 = Utf8out#30 = Utf8Ljava/io/PrintStream;#31 = Utf8java/io/PrintStream#32 = Utf8println#33 = Utf8(Ljava/lang/String;)V{public demo04.test02();// 构造方法descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1// Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 3: 0LocalVariableTable:StartLengthSlotNameSignature050thisLdemo04/test02;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: getstatic#2// Field java/lang/System.out:Ljava/io/PrintStream;3: ldc #3// String hello world5: invokevirtual #4// Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 5: 0line 6: 8LocalVariableTable:StartLengthSlotNameSignature090args[Ljava/lang/String;}SourceFile: "test02.java"

  • 常量池可以给虚拟机指令提供一些常量符号,可以通过查表的方式查到。
StringTableStringTable 的数据结构
  • hash表(数组 链表)
  • 不可扩容
  • 存字符串常量,唯一不重复
  • 每个数组单元称为一个哈希桶
  • 大小至少是 1009
面试题

String s1 = "a"; String s2 = "b"; String s3 = "a""b"; String s4 = s1s2; 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);falsetruetruefalse// 调换后,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=10: ldc #2// String a2: astore_13: ldc #3// String b5: astore_26: ldc #4// String ab8: astore_39: 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 = s1s2;

反编译结果

Code:stack=2, locals=5, args_size=10: ldc #2// String a2: astore_13: ldc #3// String b5: astore_26: ldc #4// String ab8: astore_39: new #5// class java/lang/StringBuilder12: dup13: invokespecial #6// Method java/lang/StringBuilder."<init>":()V16: aload_117: invokevirtual #7// Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;20: aload_221: 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: astore429: return

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

@Overridepublic String toString() {// Create a copy, don't share the arrayreturn new String(value, 0, count);}

所以 s3 == s4 为 false

字符串常量拼接

String s1 = "a"; // 懒惰的String s2 = "b";String s3 = "ab";String s4 = s1s2;String s5 = "a""b";

反编译结果

Code:stack=2, locals=6, args_size=10: ldc #2// String a2: astore_13: ldc #3// String b5: astore_26: ldc #4// String ab8: astore_39: new #5// class java/lang/StringBuilder12: dup13: invokespecial #6// Method java/lang/StringBuilder."<init>":()V16: aload_117: invokevirtual #7// Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;20: aload_221: 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: astore429: ldc #4// String ab31: astore533: return

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

所以 s3 == s5 为 true

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=10: new #2// class java/lang/StringBuilder3: dup4: invokespecial #3// Method java/lang/StringBuilder."<init>":()V7: new #4// class java/lang/String10: dup11: ldc #5// String a13: invokespecial #6// Method java/lang/String."<init>":(Ljava/lang/String;)V16: invokevirtual #7// Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;19: new #4// class java/lang/String22: dup23: ldc #8// String b25: invokespecial #6// Method java/lang/String."<init>":(Ljava/lang/String;)V28: 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_135: 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:gcpublic static void main(String[] args) throws InterruptedException {int i = 0;try {for (int j = 0; j < 100000; j) { // j=100, j=10000String.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, avg8.000Number of entries:4388 =105312 bytes, avg24.000Number of literals:4388 =284264 bytes, avg64.782Total 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.240226directBuffer(); // 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-privatesuper(-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 boundaryaddress = baseps - (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 Deallocatorimplements 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) {// Paranoiareturn;}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() ,直接内存不能被即时释放,可以通过直接调用 Unsafe 的 freeMemory 方法手动管理回收直接内存。

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

,
    推荐阅读
  • 奥迪被男孩儿划伤家长赔了3500(反转10岁男孩划伤)

    楚天都市报记者:刘闪近日,重庆沙坪坝区石井坡一辆奥迪车被划,车主报警后发现一位10岁男孩有“最大嫌疑”,男孩家长向车主赔偿了3500元。邹兴华:10月7日下午2点多,车主王先生报警反映,他的车被划了。鉴于孩子是未成年,我们将当事双方带回派出所进行调解。最终,由刘先生赔偿车主王先生车辆修理费用3500元。能够不把孩子的情绪当小事,不把孩子的自尊当小事,邹警官不仅是优秀的警察,也是优秀的“大人”。

  • 索的组词(索的组词有哪些)

    下面希望有你要的答案,我们一起来看看吧!索的组词索的组词:离群索居、勒索、搜索、索性、不假思索、按图索骥、搜索枯肠、敲诈勒索、线索、摸索、索取、索要、智尽能索、探赜索隐、兴味索然扥。索是一个汉字,读作suǒ,本意是指绳子,搜寻,讨要,毫无,单独等引申义是。

  • 谷歌seo实例详解(外贸自建站Google排名上不去)

    在做外贸网站时,我们很容易遇到排名不再上升的时期,其实谷歌优化主要也是做好几个内容,做好细节。2,GPB外链服务做谷歌优化外链是必不可少的,尤其是优质的GPB外链,如果自己不专业,最好是请人做好这一块。你像现在非常好的GPB外链,效果是很不错的。

  • r11s深度评测oppo(测评OPPOR11究竟好在哪儿)

    据调查机构消息,2016年OPPO挤进了国产手机的前三名。R9系列产品更是销量突破2000万台,在国内那可谓是傲视群雄,成为了年度最佳。然而机身背面和iPhone7Plus超高相似度在网络上形成了几乎一面倒的差评。但不可否认是,在抄袭iPhone7Plus设计的同时,OPPO还是做出了显著的进步。显示方面,5.5英寸1080p分辨率的AMOLED屏幕正是能够做到轻薄机身的基础。

  • 纸上谈兵主人公(单刀赴会的主人公是谁)

    赵括之所以出名,是因为长平之战前夕,赵王中了秦国的离间计,临时撤换了廉颇,派赵括出战。那时候,东方的魏、齐等国相继衰弱,赵国成为有可能对抗秦国的唯一一股力量。一气之下,赵奢斩杀了平原君家的九个管家,以示惩戒。当时,廉颇和乐乘都认为道路艰险,救援难度大,不应出兵。恃才而骄、不重实际让赵奢对这个儿子的担心日益加重。赵括要用行动证明自己并不是只会纸上谈兵。

  • 女人善良优雅经典句子(这些都合适摘录)

    女人善良优雅经典句子红尘中,你想做与众不同的时尚女人吗?那份独到的韵味,当然需要一款清新淡雅的饰品来点缀属于你的风情。一种是太阳,一种是你努力的模样!清澈明亮的瞳孔,弯弯的柳眉,长长的睫毛微微地颤动着,白皙无瑕的皮肤透出淡淡红粉,薄薄的双唇如玫瑰花瓣娇嫩欲滴。女人,越是处在逆境的时候,越要把背梁骨挺得直直的,脸上始终持着明亮的笑容,身上穿上合体的衣服,在人生的舞台上展示自我顽强的魅力。

  • 灭蚊灯灭蚊到底有没有效果(灭蚊灯管用吗要知道)

    我们看到灭蚊灯的一些宣传上,商家打的都是紫外线来灭蚊这种宣传语,权威部门已经做出结论,紫外线灭蚊其实是一个笑话。你还会相信紫外线灭蚊能有效吗?说了这么多,其实就是想告诉大家,虽然紫外线灭蚊灯没有什么效果,但是市面上其它的一些物理性的灭蚊灯,还是有一定效果的。

  • 战疫必胜全靠自我(并肩战疫守望相助)

    截至2月25日,江西省已累计派出9批11支医疗队,共计1233名队员投入到支援湖北疫情防治工作中。截至2月25日,江西援随医疗队接管12个病区,累计管理患者526例,其中疑似患者46例,确诊患者480例,累计重症患者25例,危重症患者39例;累计出院患者235例,其中疑似患者出院46例。2月14日,一则暖心的消息刷屏网络:江西援随医疗队接生疫情期间首例新生儿。而这也是疫情期间江西医疗队帮助湖北接生的第一个新生儿。

  • 巧克力怎么吃(正确吃巧克力的技巧)

    下面希望有你要的答案,我们一起来看看吧!巧克力怎么吃吃巧克力后可以再来一杯红茶,餐后3小时左右吃上两块巧克力能快速缓解饥饿感,也可以补充能量。但巧克力毕竟是高营养食品,应该把它当作日常饮食中的一部分而不是额外的零食,以免热量超标,感到饥饿又没有时间吃饭时可以吃几小块黑巧克力。

  • 洗衣机哪个品牌最好最实用(洗衣机十大排名)

    空气阻尼吊杆能够减少碰撞降低噪音,改善了波轮洗衣机噪音大的缺陷,过滤网能够将毛绒脏污一网打尽,避免了二次污染,同时延长了洗衣机寿命。桶自洁程序让机器可以自动清洁内外桶壁的污垢,避免了二次污染。