当前位置:网站首页>【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对象来分配、回收内存。
边栏推荐
- Lombok makes ⽤ @data and @builder's pit at the same time. Are you hit?
- View remote test data and records anytime, anywhere -- ipehub2 and ipemotion app
- 深度学习之线性代数
- Levels - UE5中的暴雨效果
- 集合(泛型 & List & Set & 自定义排序)
- stm32F407-------SPI通信
- QT tutorial: creating the first QT program
- 【YoloV5 6.0|6.1 部署 TensorRT到torchserve】环境搭建|模型转换|engine模型部署(详细的packet文件编写方法)
- Service asynchronous communication
- C9高校,博士生一作发Nature!
猜你喜欢
View remote test data and records anytime, anywhere -- ipehub2 and ipemotion app
equals()与hashCode()
Data analysis course notes (V) common statistical methods, data and spelling, index and composite index
迈动互联中标北京人寿保险,助推客户提升品牌价值
Attention SLAM:一种从人类注意中学习的视觉单目SLAM
【软件逆向-自动化】逆向工具大全
随时随地查看远程试验数据与记录——IPEhub2与IPEmotion APP
第六篇,STM32脉冲宽度调制(PWM)编程
Win10 startup error, press F9 to enter how to repair?
Explain in detail the matrix normalization function normalize() of OpenCV [norm or value range of the scoped matrix (normalization)], and attach norm_ Example code in the case of minmax
随机推荐
Matlab learning notes
How to set encoding in idea
Memory optimization of Amazon memorydb for redis and Amazon elasticache for redis
Advanced learning of MySQL -- basics -- multi table query -- self join
Leetcode (547) - number of provinces
ZABBIX 5.0: automatically monitor Alibaba cloud RDS through LLD
Dell筆記本周期性閃屏故障
深度学习简史(二)
Slow database query optimization
深度学习之数据处理
stm32F407-------DAC数模转换
fastDFS数据迁移操作记录
Interface (interface related meaning, different abstract classes, interface callback)
【批处理DOS-CMD命令-汇总和小结】-查看或修改文件属性(ATTRIB),查看、修改文件关联类型(assoc、ftype)
批量获取中国所有行政区域经边界纬度坐标(到县区级别)
QT tutorial: creating the first QT program
省市区三级坐标边界数据csv转JSON
Explain in detail the matrix normalization function normalize() of OpenCV [norm or value range of the scoped matrix (normalization)], and attach norm_ Example code in the case of minmax
Lombok makes ⽤ @data and @builder's pit at the same time. Are you hit?
[software reverse - solve flag] memory acquisition, inverse transformation operation, linear transformation, constraint solving