当前位置: 首页 > 科技观察

去京东面试问我JVM堆外内存是什么,我直接麻了,赶紧复习

时间:2023-03-13 19:23:55 科技观察

去京东面试,问我什么是JVM堆外内存,大家可能经常会遇到堆外内存,但是很多人可能不知道什么是堆外内存,那么今天小编就来给大家深入分析一下。什么是堆内存?要说这个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等中间件因为要管理大量的内存数据,一般会选择申请一块堆外内存,把数据放到里面,自己进行精细化管理。//定义要申请的堆外内存大小,这里是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对象被回收,其关联的堆外内存就会被释放。如下图所示:好了,今天的知识点就分享到这里。相信看完之后,你应该对堆外内存的概念有了更清晰的认识。