前言
一般认为,Java对象都是在堆上分配的,但也有一些特殊情况。比如NIO的直接内存,逃逸分析和TLAB,接下来详细解释下这三种。
直接内存
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区域。
当数据需要通过网络发送到远程服务器时,使用堆内存需要进行额外的复制步骤,因为受GC影响堆内存地址会变化,所以需要一个固定的内存地址。首先,数据从堆内存复制到直接内存(堆外内存),然后再从直接内存发送到远程服务器。这个过程涉及了两次内存复制操作,可能会引入一些额外的开销和延迟。
而使用堆外内存,则可以省略掉这个额外的复制步骤。数据可以直接从堆外内存发送到远程服务器,避免了中间的复制操作,从而提高了数据传输的速度和效率。但是这部分内存被频繁地使用,可能导致OutOfMemoryError 异常出现,堆外内存难以控制,如果内存泄漏,那么很难排查。
直接内存不受 GC(新生代的 Minor GC) 影响,只有当执行老年代的 Full GC 时候才会顺便回收直接内存。
可以用DirectByteBuffer操作直接内存:
public static void main(String[] args) {
// public static ByteBuffer allocateDirect(int capacity) {
// return new DirectByteBuffer(capacity);
//}
// 分配直接内存,大小为1MB
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024 * 1024);
// 写入数据到直接内存
String data = "Hello, Direct Memory!";
directBuffer.put(data.getBytes());
// 从直接内存读取数据
directBuffer.flip();
byte[] buffer = new byte[directBuffer.limit()];
directBuffer.get(buffer);
System.out.println(new String(buffer));
// 释放直接内存
directBuffer.clear();
}
逃逸分析
逃逸分析是Java虚拟机进行优化的一项技术,它可以分析方法中的对象引用,判断这些引用是否仅在方法内使用,从而决定是否将这些对象分配在堆上,可以把他理解为堆逃逸
。
如果一个对象的引用在方法内使用,并不会被传递到方法之外,可以被优化为栈上分配或标量替换等方式,这样该对象所占用的内存空间就可以随方法栈帧出栈而销毁,就减轻了垃圾回收的压力。相反,如果对象的引用将会被传递到方法之外,例如作为另一个对象的属性或返回给调用方法的方法外部,那么就必须将这个对象分配在堆上,以便其他线程也能够访问这个对象。
逃逸分析技术可以减少Java虚拟机在堆上分配对象的数量,从而减轻垃圾回收器的压力,提高程序的性能。
以下例子分析逃逸的可能性:
public user test1(){
User user = new OrderService();
// 对象的引用被传递到方法外了,不符合堆逃逸分析,分配到堆上
return user;
}
public void test1(){
// 对象的引用只在方法内部,符合了堆的逃逸分析,可以分配到栈上。
User user = new User();
System.out.println(user);
}
标量替换:通过逃逸分析确定该对象不会被外部访问,将该对象成员变量分解若干个被这个方法使用的成员变量所代替,这样就不会因为没有一大块连续空间导致对象内存不够分配。
TLAB分配
其实TLAB是在堆内画了一块空间,新建对象分配到TLAB中,按道理来说新建的对象也分配到了堆中。
TLAB解释:
是Java虚拟机为每个线程分配的一块内存缓冲区,用于快速分配对象。TLAB通过为每个线程分配私有的内存缓冲区,使得每个线程都可以在自己的TLAB上分配对象,而无需竞争堆内存的分配指针。这样一来,多线程之间的分配操作可以并行进行,减少了竞争和同步的开销,提高了分配的性能。
通过XX:+UseTLAB参数来设定虚拟机是否使用TLAB(JVM会默认开启XX:+UseTLAB),XX:TLABSize 指定TLAB大小。