JVM内存结构(3)https://developer.aliyun.com/article/1530771
5.6 StringTable垃圾回收
-Xmx18m -XX:+PrintStringTableStatistics -XX: +PrintGCDetails -verbose:gc
摄者虚拟机参数,分别是设置虚拟机堆内存的最大值,打印字符串表的统计信息,打印垃圾回收的详细信息
桶的数量, 总占用空间等信息
类名啊,方法名啊也是以字符串常量存储
添加了一万多个对象,但是只有7千多个,因为内存不足触发垃圾回收
5.7 StringTable 性能调优
底层是一个hash表,性能和大小相关
太小的话就会容易出现hash冲突
因为StringTable是由HashTable实现的,所以可以适当增加HashTable桶的个数,来减少字符串放入串池所需要的时间
-XX:StringTableSize=xxxx //最低为1009
- 考虑是否将字符串对象入池
- 可以通过intern方法减少重复入池,保证相同的地址在StringTable中只存储一份
6.直接内存
6-1 定义
- 常见于NIO操作,用于数据缓冲区
- 分配回收成本较高,但读写性能高
- 不受JVM 内存回收影响
这里系统缓冲区复制到java缓冲区造成了不必要的复制
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); } }
这里是使用了directbuffer 之后的效果
操作系统画出来一片缓冲区,这片缓冲区java代码可以直接访问,这样少了一次缓冲区的复制操作
直接内存溢出是Direct buffer memory
6.2 分配和回收原理
- 使用了Unsafe对象完成直接内存的分配和回收,并且回收需要主动调用freeMemory方法
- ByteBuffer的实现类内部,使用Cleaner(虚引用)来监测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过
Cleaner的clean方法调用freeMemory来释放直接内存
/** * 禁用显式回收对直接内存的影响 */ public class Demo1_26 { static int _1Gb = 1024 * 1024 * 1024; /* * -XX:+DisableExplicitGC 显式的 */ public static void main(String[] args) throws IOException { ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1Gb); System.out.println("分配完毕..."); System.in.read(); System.out.println("开始释放..."); byteBuffer = null; System.gc(); // 显式的垃圾回收,Full GC System.in.read(); } }
直接内存的释放不能通过垃圾回收只能通过底层的unsafe对象的freeMemory方法
/** * 直接内存分配的底层原理:Unsafe */ public class Demo1_27 { 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); } } }
当bytebuffer被回收时就会触发虚引用对象cleaner 中的create方法,然后会调用回调任务对象Delocator就会调用freeMemory方法
有时候我们会-XX:+DisableExplicitGC显式的
禁用显示的垃圾回收,因为这个很损耗性能,这样也会间接的让我们无法手动直接调用垃圾回收拉释放直接内存
这时候我们可以使用usafe
对象来释放直接内存
(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); } }
}
[外链图片转存中...(img-gIZ18FLN-1696576900268)] 当bytebuffer被回收时就会触发虚引用对象cleaner 中的create方法,然后会调用回调任务对象Delocator就会调用freeMemory方法 有时候我们会`-XX:+DisableExplicitGC显式的` 禁用显示的垃圾回收,因为这个很损耗性能,这样也会间接的让我们无法手动直接调用垃圾回收拉释放直接内存 这时候我们可以使用`usafe`对象来释放直接内存