什么是堆内存?JVM堆内存是怎么划分的?JVM堆内存满了怎么办?基于堆外内存解决系统GC卡死问题,今天给大家讲一个很有意思的知识,堆外内存。大家平时出去面试或者研究一些技术的时候,可能经常会遇到堆外内存,但是很多人可能不知道什么是off-heap堆外内存。什么,那么今天就给大家深度解析一下。什么是堆内存?要说这个off-heap堆外内存,首先得说on-heap也是堆上内存。这个on-heap堆内存相信很多人应该都不陌生。也就是说,我们平时写的Java系统,运行起来其实就是一个JVM进程。这个JVM进程有一个专用于它的内存空间。这个内存空间就是堆内存。大致如下图所示:JVM堆内存是怎么划分的?那么这里通常会出现什么问题呢?一般来说,没有什么大问题,但是如果需要在JVM堆内存中缓存大量的数据,就可能会出现问题。所谓数据缓存,就是在堆内存中存放了大量的数据。这些数据必须一直使用,所以一般来说,它们是不能被回收的,所以很多数据可能会留在JVM的堆内存中。.如下图:那么下一个问题来了,这个JVM中的堆内存是划分的,一块区域是新生代,一块区域是老年代。因为像这样的缓存数据是长期存放在堆内存中的,所以通常会在新生代停留一段时间,因为无法被垃圾回收,所以才会放到老年代。这时出现下图:JVM堆内存满了怎么办?但是如果过多的缓存数据放在这个老年代,可能会导致剩余的可用空间变少,这可能会导致老时代经常被其他一些数据填满。一旦满了就会触发JVM的FullGC,一个垃圾回收线程回收旧时代的数据。这时候的画面是这样的:但是一般来说,这时候可以回收的是缓存数据以外的一些空间。即使你回收了,缓存的数据也会一直存在,所以无法回收。这时候你每回收一部分剩余空间,缓存的数据还是会剩下很多。这时候缓存的数据在老年代会一直占据很大的空间。那么这个时候必然会导致一个现象,就是老年代频繁的写一点数据就满了,再写一点数据就满了,然后就得触发一次FullGC尽管。每次FullGC都会导致JVM停止运行,无法处理外部请求。这时候对外,你会感觉你的系统性能经常抖动,一会卡,一会卡。因此,一般来说,在JVM内部缓存大量数据很可能会导致上述现象,即老年代被频繁填满,频繁触发fullGC,系统频繁停止无法处理请求。如下图所示:基于堆外内存解决系统GC卡死问题那么在这种情况下,我们的优化方法是将需要缓存的数据从JVM堆内存转移到offheap堆外内存,那么问题来了,什么是堆外内存?顾名思义,不由JVM管理的内存区域,以及由OS操作系统管理的一部分内存称为堆外内存。所以我们其实可以选择将大量数据直接写入堆外内存。这样JVM堆中的老年代空间就不会被占用,老年代就不会被频繁填满,也就不会频繁触发FullGC。导致系统性能频繁抖动。如下图所示:既然这个堆外内存这么好,那么问题来了,它的缺点是什么?当然,因为如果你使用的是JVM堆中的内存,当你写了很多数据之后,如果内存满了,这时候JVM会自动进行垃圾回收,帮你释放一些内存空间,完全自动的。但是如果你使用的是堆外内存,就没有JVM帮你管理。这时,你必须自己管理那块内存空间。也就是说,你写完数据之后,你要注意在需要的时候释放一部分内存,这样就导致了堆外内存。虽然不会导致你的JVM频繁GC,但是可能会让你的代码管理难度变高。如下图所示:那么我们一般如何用Java代码来申请这块堆外内存呢?看下面的代码,一般类似于Netty、RocketMQ等中间件,因为要管理大量的内存数据,所以会选择申请一块堆外内存Memory,将数据放入其中,进行精细化管理由你自己。//定义要申请的堆外内存大小,这里是1GBintmemorySize=1024*1024*1024;//使用Java中的ByteBuffer.allocateDirect方法申请一块堆外内存ByteBufferbyteBuffer=ByteBuffer.allocateDirect(memorySize);//将数据写入堆外内存byte[]bytes="helloworld".getBytes();byteBuffer.put(bytes);//从堆外内存中读取数据byteBuffer.flip();byte[]readBytes=newbyte[bytes.length];byteBuffer.get(readBytes,0,bytes.length);那么你可以看到我们是如何申请堆外内存的,通过这段代码是如何向堆外内存写入数据之后,又是如何读取数据的,现在想想我们应该如何释放堆外内存呢?是这样的,这段堆外内存实际上是被JVM堆中的一个ByteBuffer对象引用的,所以如果在JVM堆中回收了ByteBuffer对象,那么它关联的堆外内存就会被释放。如下图所示:好了,今天的知识点就分享到这里。相信看完之后,你应该对堆外内存的概念有了更清晰的认识。
