当前位置:网站首页>【JVM调优实战100例】05——方法区调优实战(下)
【JVM调优实战100例】05——方法区调优实战(下)
2022-07-06 17:11:00 【半旧518】
前 言
作者简介:半旧518,长跑型选手,立志坚持写10年博客,专注于java后端
专栏简介:实战案例驱动介绍JVM知识,教你用JVM排除故障、评估代码、优化性能
文章简介:介绍方法区概念、帮助你深入理结直接内存
7.8 直接内存
直接内存由操作系统来管理。常见于NIO,用于数据缓冲,读写性能很高,分配回收花销较高。
使用以下代码来比较使用传统方式读写与NIO读写的区别,注意第一次启动读写性能会较差,需多运行几次,计算平均值。
/**
* 演示 ByteBuffer 作用
*/
public class Demo1_9 {
static final String FROM = "F:\\博客\\谷粒学院实践项目.md";
static final String TO = "F:\\谷粒学院实践项目.md";
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);
}
}
为什么直接内存读写效率高?使用阻塞式io进行读写cpu和内存的变化如下图。很显然,从系统缓存区将文件复制到java缓存区是一个耗时且不必要的复制。
使用Nio进行读写cpu和内存的变化如下图。操作系统在allocateDirect()方法执行时会分配一块直接内存,这部分内存java代码和系统都可以进行访问。
7.9 直接内存的内存溢出问题
直接内存direct memory并不由jvm进行垃圾回收,可能导致内存泄漏问题。运行如下代码。
/**
* 演示直接内存溢出
*/
public class Demo1_10 {
static int _100Mb = 1024 * 1024 * 100;
public static void main(String[] args) {
List<ByteBuffer> list = new ArrayList<>();
int i = 0;
try {
while (true) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb);
list.add(byteBuffer);
i++;
}
} finally {
System.out.println(i);
}
// 方法区是jvm规范, jdk6 中对方法区的实现称为永久代
// jdk8 对方法区的实现称为元空间
}
}
输出结果。
72
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:695)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
at cn.itcast.jvm.t1.direct.Demo1_10.main(Demo1_10.java:19)
直接内存的底层回收机制是怎样的呢?运行以下代码。
/**
* 禁用显式回收对直接内存的影响
*/
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();
}
}
在控制台输出分配完毕后,从后台的任务管理器可以看到其内存占用情况。
当在控制台输入回车,输出开始释放,再输入回车,这个占用1个G的内存的进程就被清理了。是不是意味着java的gc操作发生了作用呢?
下面我们来解析上面直接内存回收的过程。Unsafe是jdk底层的一个类,用于内存分配,内存回收等,一般普通程序员无需使用,这里我们通过反射获取Unsafe对象,演示直接内存分配的底层原理。
/**
* 直接内存分配的底层原理: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);
}
}
}
运行代码,在任务管理器观察jdk进程内存占用发现,内存占用会在allocateMemory()后增加1G,在freeMemory()后恢复。因此,直接内存的回收其实不是由jvm虚拟机完成,而是通过Unsafe对象调用freeMemory()完成。
下面查看ByteBuffer类的源码来验证我们的观点。
allocateDirect()中返回一个DirectByteBuffer对象。
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
调用Unsafe中allocateMemory()来实现申请内存,新建Cleaner对象来释放内存。
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);
} 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));
att = null;
}
cleaner中关联的Deallocator是什么?点进去看发现它实现了Runnable,是回调任务对象,在run方法中调用了Unsafe的freeMemory。
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);
}
}
那么垃圾回收的任务什么时候被执行的呢?看Cleaner源码。
public class Cleaner
extends PhantomReference<Object> {
//...
public void clean() {
if (!remove(this))
return;
try {
thunk.run();
} catch (final Throwable x) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (System.err != null)
new Error("Cleaner terminated abnormally", x)
.printStackTrace();
System.exit(1);
return null;
}});
}
}
//...
}
原来Cleaner是java中的虚引用类型,当它的绑定的对象被垃圾回收时,会触发虚引用的clean()方法,执行回调方法run()。
下面回过头看DirectByteBuffer类中的Cleaner创建,过程就清楚了。
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
总结直接内存分配、释放的的过程就是:通过调用Unsafe的allocateMemory来分配直接内存,通过创建虚引用对象Cleaner对象,将DirectoryByteBuffer与回调任务绑定,当Directory被垃圾回收时,会自动执行Cleaner的clean()方法,来调用Unsafe的freeMemory()释放内存。
7.10 禁用显式垃圾回收对直接内存的影响
在java中可以采用System.gc()来显式的建议jvm进行垃圾回收,但这种垃圾回收方式是Full GC,既会进行新生代的回收,也会进行老年代的回收。可能会影响程序性能。为了避免程序员误用,可以使用-XX +DisableExplctGC
来禁用显示的垃圾回收。
在禁用了显式垃圾回收后再次运行Demo1_26。
/**
* 禁用显式回收对直接内存的影响
*/
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();
}
}
以上代码的直接内存并没有被回收,这是因为显式的垃圾回收失效。bytebuffer不会被垃圾回收,进而导致直接内存无法被释放,只有在程序被动进行Full GC时进行垃圾回收。如果在程序需要频繁使用直接内存的情况,我们可以收到使用Unsafe对象来分配、回收内存。
边栏推荐
- [Batch dos - cmd Command - Summary and Summary] - String search, find, Filter Commands (FIND, findstr), differentiation and Analysis of Find and findstr
- Zabbix 5.0:通过LLD方式自动化监控阿里云RDS
- Interface (interface related meaning, different abstract classes, interface callback)
- OSPF configuration command of Huawei equipment
- Data processing of deep learning
- 学习光线跟踪一样的自3D表征Ego3RT
- AI super clear repair resurfaces the light in Huang Jiaju's eyes, Lecun boss's "deep learning" course survival report, beautiful paintings only need one line of code, AI's latest paper | showmeai info
- Dr selection of OSPF configuration for Huawei devices
- build. How to configure the dependent version number in the gradle file
- Notes of training courses selected by Massey school
猜你喜欢
Deep understanding of distributed cache design
Data analysis course notes (V) common statistical methods, data and spelling, index and composite index
Lombok makes ⽤ @data and @builder's pit at the same time. Are you hit?
Alexnet experiment encounters: loss Nan, train ACC 0.100, test ACC 0.100
.class文件的字节码结构
Linear algebra of deep learning
【批處理DOS-CMD命令-匯總和小結】-字符串搜索、查找、篩選命令(find、findstr),Find和findstr的區別和辨析
Configuring the stub area of OSPF for Huawei devices
Attention SLAM:一种从人类注意中学习的视觉单目SLAM
集合(泛型 & List & Set & 自定义排序)
随机推荐
重上吹麻滩——段芝堂创始人翟立冬游记
【YoloV5 6.0|6.1 部署 TensorRT到torchserve】环境搭建|模型转换|engine模型部署(详细的packet文件编写方法)
Dr selection of OSPF configuration for Huawei devices
Attention slam: a visual monocular slam that learns from human attention
Attention SLAM:一种从人类注意中学习的视觉单目SLAM
Matlab learning notes
英雄联盟|王者|穿越火线 bgm AI配乐大赛分享
View remote test data and records anytime, anywhere -- ipehub2 and ipemotion app
Telerik UI 2022 R2 SP1 Retail-Not Crack
腾讯云 WebShell 体验
深度学习简史(二)
Advanced learning of MySQL -- Fundamentals -- four characteristics of transactions
Deeply explore the compilation and pile insertion technology (IV. ASM exploration)
The printf function is realized through the serial port, and the serial port data reception is realized by interrupt
5种不同的代码相似性检测,以及代码相似性检测的发展趋势
省市区三级坐标边界数据csv转JSON
Deep understanding of distributed cache design
Mujoco finite state machine and trajectory tracking
dynamic programming
[yolov5 6.0 | 6.1 deploy tensorrt to torch serve] environment construction | model transformation | engine model deployment (detailed packet file writing method)