jvm的内存回收机制,jvm内存管理机制和垃圾回收机制

  jvm的内存回收机制,jvm内存管理机制和垃圾回收机制

  

目录

JVM内存模型如何分配堆外内存第一种方式:ByteBuffer # allocateDirect第二种方式:Unsafe#allocateMemory如何回收堆外内存第一种方式:Unsafe#freeMemory第二种方式:JVM回收堆外内存注1:注2:报价摘要

 

  00-1010在JVM中,内存分为两块,即堆内内存和堆外内存。堆内内存是JVM使用的内存,堆外内存是JVM不使用的内存,一般分配给机器。

  那么整个内存模型如下:

  所以在JVM中,唯一可以正常分配的内存就是堆内存。我们知道,JVM并不建议开发者直接操作堆外内存,这样很容易造成内存泄漏,也很难检查。然而,在JVM中,操作堆外内存和回收堆外内存是可能的,但不建议这样做。

  00-1010那么如何在堆内存中分配堆外内存呢?

  Java中分配堆外内存有两种方式,分别是ByteBuffer#allocateDirect和Unsafe#allocateMemory。

  可能第一个会经常用。这是Java NIO提供的一个内存分配类,在做网络开发的时候经常用来分配内存,而第二个是不安全的。我们知道Unsafe是提供给开发者操作最底层数据的类,类似于C或C直接操作内存的方式,所以不推荐这个类。如果用这个类来分配内存但没有及时回收,就容易导致内存泄漏。

  00-1010这种内存分配实现如下:

  //分配10M内存byte buffer byte buffer=byte buffer . allocated direct(10 * 1024 * 1024);其实堆外最底层的内存分配是Unsafe#allocateMemory,ByteBuffer只是封装了Unsafe。

  

JVM内存模型

公共类测试{私有静态不安全不安全=null公共静态void main (string [] args)抛出Nosuchfield异常,IllegalAccess异常{//分配10M内存字段get unsafe=unsafe . class . getdeclaredfield( the unsafe );get unsafe . set accessible(true);Unsafe=(Unsafe)get Unsafe . get(null);//内存分配后返回的内存地址是long address=unsafe . allocate memory(10 * 1024 * 1024);}}这样,Unsafe类不能直接使用,但是可以通过反射使用。类分配内存后,需要手动回收,否则分配的内存不会被释放。

 

  00-1010说了如何分配内存,然后继续学习如何回收堆外内存。

  00-1010在分配堆外内存的两种方式中,第二种不安全的方式实际上提供了释放堆外内存的实现,如下所示:

  公共类测试{ private static Unsafe Unsafe=null;公共静态void main (string [] args)抛出Nosuchfield异常,IllegalAccess异常{//分配10M内存字段get unsafe=unsafe . class . getdeclaredfield( the unsafe );get unsafe . set accessible(true);Unsafe=(Unsafe)get Unsafe . get(null);//内存分配后返回的内存地址是long address=unsafe . allocate memory(10 * 1024 * 1024);//回收分配的堆外内存

   unsafe.freeMemory(address); }}在Unsafe中提供了freeMemory的实现进行回收堆外内存,但是前提是需要知道被分配的堆外内存地址才可以实现对应的内存回收。

  这种回收堆外内存的方式其实是开发者自己手动回收,并不是由JVM引起的内存回收,那么JVM如何回收堆外内存呢?

  

 

  

第二种方式:JVM回收堆外内存

通过ByteBuffer#allocateDirect分配的堆外内存在JVM中其实也是存在一定的内存占用的,具体关联关系如下:

 

  

 

  当通过ByteBuffer#allocateDirect分配堆外内存后,会将堆外内存的地址、大小等信息通过DirectByteBuffer进行关联,那么堆内存中就可以关联到堆外内存。

  那么Cleaner又是什么东西呢?

  了解Cleaner需要知道JVM中四种引用方式:强引用、弱引用、软引用、虚引用,Cleaner就是虚引用的实现,上图中的ReferenceQueue就是一个引用队列,将需要回收的Cleaner放入到该队列中,实现逻辑如下:

  JVM执行Full GC时会将DirectByteBuffer进行回收,回收之后Clearner就不存在引用关系再下一次发生GC时会将Cleaner对象放入ReferenceQueue中,同时将Cleaner从链表中移除最后调用unsafe#freeMemory清除堆外内存那么可能会存在疑问,为什么DirectByteBuffer 会被回收呢?

  首先DirectByteBuffer 是存在堆内存中的对象,那么既然存在堆内存中就会发生GC晋级,即晋升到老年代中,在老年代中就会发生Full GC或者Old GC。

  

 

  

注意点

 

  

注意点1:

在实际使用DirectByteBuffer 时要避免把内存使用完,但是在实际操作中我们可能不知道堆外内存还剩余多少,因此我们可以在JVM中通过参数控制,通过JVM参数 -XX:MaxDirectMemorySize 指定堆外内存的上限大小,当超过指定的内存上限大小时,会主动触发一次Full GC进行回收内存。

 

  

 

  

注意点2:

通过DirectByteBuffer 分配内存时,可能会出现分配内存不够的情况,因此JVM如果发现堆外内存分配不足时,也会主动发起一次GC,只不过这次GC是通过System.gc() 实现的强制GC,但是在实际生产环境中我们都是通过JVM参数 -XX:+DisableExplicitGC,禁止使用System.gc()的,因此在实际使用过程中一定要注意分配内存的情况,避免出现内存泄漏。

 

  

 

  

引用

Netty 核心原理剖析与 RPC 实践

 

  

总结

到此这篇关于JVM分配和回收堆外内存的方式与注意点的文章就介绍到这了,更多相关JVM分配回收堆外内存内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

 

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: