JVM分配和回收堆外内存的方式与注意点

目录
  • JVM内存模型
  • 如何分配堆外内存
    • 第一种方式:ByteBuffer#allocateDirect
    • 第二种方式:Unsafe#allocateMemory
  • 如何回收堆外内存
    • 第一种方式:Unsafe#freeMemory
    • 第二种方式:JVM回收堆外内存
  • 注意点
    • 注意点1:
    • 注意点2:
  • 引用
  • 总结

JVM内存模型

在JVM中内存被分成两大块,分别是堆内存和堆外内存,堆内存就是JVM使用的内存,而堆外内存就是非JVM使用的内存,一般是分配给机器使用的内存。

那么整个内存模型如下:

因此在JVM中正常只能分配之际独有的内存即堆内存,而我们知道JVM并不建议开发者直接操作堆外内存的,因此容易造成内存泄漏,并且难以排查,但是在JVM中是可以操作堆外内存的并且也可以回收堆外内存,但是是一种不建议的方式。

如何分配堆外内存

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

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

可能第一个会经常使用到,这是Java NIO提供的一个分配内存的类,在做网络开发时会经常使用该方式进行分配内存,而第二种方式是Unsafe的方式,我们知道Unsafe是一种不安全的类,该类是提供给开发者操作最底层数据的类,类似C或者C++直接操作内存的方式,因此该类并不建议使用,如果使用该类分配内存但是没有及时回收容易造成内存泄漏。

第一种方式:ByteBuffer#allocateDirect

该类分配内存的实现方式如下:

//分配10M的内存
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(10 * 1024 * 1024);

通过该方式分配堆外内存其实最底层还是使用的是Unsafe#allocateMemory进行分配内存,ByteBuffer只是对Unsafe做了一层封装。

第二种方式:Unsafe#allocateMemory

public class Test {
    private static Unsafe unsafe = null;
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        //分配10M的内存
        Field getUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        getUnsafe.setAccessible(true);
        unsafe = (Unsafe)getUnsafe.get(null);
        //分配完内存返回内存的地址
        long address = unsafe.allocateMemory(10 * 1024 * 1024);
    }
}

该方式中Unsafe类并不能直接被使用,但是可以通过反射的方式使用该类,该类分配内存后需要手动回收,不然被分配的内存不会被释放。

如何回收堆外内存

说完了如何分配内存,那么继续了解如何回收堆外内存。

第一种方式:Unsafe#freeMemory

分配堆外内存的两种方式中,第二种Unsafe的方式其实提供了一个释放堆外内存的实现,实现如下:

public class Test {
    private static Unsafe unsafe = null;
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        //分配10M的内存
        Field getUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        getUnsafe.setAccessible(true);
        unsafe = (Unsafe)getUnsafe.get(null);
        //分配完内存返回内存的地址
        long address = unsafe.allocateMemory(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分配回收堆外内存内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JVM堆外内存源码完全解读分析

    概述 广义的堆外内存 说到堆外内存,那大家肯定想到堆内内存,这也是我们大家接触最多的,我们在jvm参数里通常设置-Xmx来指定我们的堆的最大值,不过这还不是我们理解的Java堆,-Xmx的值是新生代和老生代的和的最大值,我们在jvm参数里通常还会加一个参数-XX:MaxPermSize来指定持久代的最大值,那么我们认识的Java堆的最大值其实是-Xmx和-XX:MaxPermSize的总和,在分代算法下,新生代,老生代和持久代是连续的虚拟地址,因为它们是一起分配的,那么剩下的都可以认为是堆外内存

  • 浅谈Java堆外内存之突破JVM枷锁

    对于有Java开发经验的朋友都知道,Java中不需要手动的申请和释放内存,JVM会自动进行垃圾回收:而使用的内存是由JVM控制的. 那么,什么时机会进行垃圾回收,如何避免过度频繁的垃圾回收?如果JVM给的内存不够用,怎么办? 此时,堆外内存登场!利用堆外内存,不仅可以随意操控内存,还能提高网络交互的速度. 背景1:JVM内存的分配 对于JVM的内存规则,应该是老生常谈的东西了,这里我就简单的说下: 新生代:一般来说新创建的对象都分配在这里. 年老代:经过几次垃圾回收,新生代的对象就会放在年老代里

  • JVM分配和回收堆外内存的方式与注意点

    目录 JVM内存模型 如何分配堆外内存 第一种方式:ByteBuffer#allocateDirect 第二种方式:Unsafe#allocateMemory 如何回收堆外内存 第一种方式:Unsafe#freeMemory 第二种方式:JVM回收堆外内存 注意点 注意点1: 注意点2: 引用 总结 JVM内存模型 在JVM中内存被分成两大块,分别是堆内存和堆外内存,堆内存就是JVM使用的内存,而堆外内存就是非JVM使用的内存,一般是分配给机器使用的内存. 那么整个内存模型如下: 因此在JVM中

  • 详细总结Java堆栈内存、堆外内存、零拷贝浅析与代码实现

    一.堆栈内存 堆栈内存,顾名思义,指的是堆内存以及栈内存,其中,堆内存是由Java GC进行管理的内存区域,而栈内存则是线程内存.关于栈内存,这里不去细说.以Hotspot为例,堆内存的简要结构如下图所示: 而堆栈的关系,我们可以通过一行简单的代码来理解: public static void main(String[] args) { Object o = new Object(); } 上述代码主要完成了两件事,new Object( ) 在堆上开辟了一块内存,也就是说,new Object

  • 深入了解java内存分配和回收策略

    一.导论 java技术体系中所提到的内存自动化管理归根结底就是内存的分配与回收两个问题,之前已经和大家谈过java回收的相关知识,今天来和大家聊聊java对象的在内存中的分配.通俗的讲,对象的内存分配就是在堆上的分配,对象主要分配在新生代的Eden上(关于对象在内存上的分代在垃圾回收中会补上,想了解的也可以参考<深入理解java虚拟机>),如果启动了本地线程分配缓冲,讲按线程优先在TLAB上分配.少数情况下也是直接在老年代中分配. 二.经典的分配策略 1.对象优先在Eden上分配 一般情况下对

  • 解析Java内存分配和回收策略以及MinorGC、MajorGC、FullGC

    目录 对象内存分配与回收策略 对象何时进入新生代.老年代 三种GC介绍 MinorGC Major GC/Full GC: 图示GC过程 对象内存分配与回收策略 对象的内存分配,往大方向讲,就是在堆上分配[但也可能经过JIT编译后被拆散为标量类型并间接地栈上分配),对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配.少数情况下也可能会直接分配在老年代中. 对象优先分配在Eden区,当Eden区可用空间不够时会进行MinorGC 大对象直接进入老年代:大对

  • Java虚拟机内存分配与回收策略问题精细解读

    本文参考于<深入理解Java虚拟机> 内存分配与回收策略 Java技术体系的自动内存管理,最根本的目标是自动化地解决两个问题:自动给对象分配内存以及自动回收分配给对象的内存. 1. 综述 对象的内存分配,从概念上讲,应该都是在堆上分配(而实际上也有可能经过即时编译后被拆散为标量类型并间接地在栈上分配).在经典分代的设计下,新生对象通常会分配在新生代中,少数情况下(例如对象大小超过一定阈值)也可能会直接分配在老年代.对象分配的规则并不是固定的,<Java虚拟机规范>并未规定新对象的创

  • 详解CLR的内存分配和回收机制

    一.CLR CLR:即公共语言运行时(Common Language Runtime),是中间语言(IL)的运行时环境,负责将编译生成的MSIL编译成计算机可以识别的机器码,负责资源管理(内存分配和垃圾回收等). 可能有人会提问:为什么不直接编译成机器码,而要先编译成IL,然后在编译成机器码呢? 原因是:计算机的操作系统不同(分为32位和64位),接受的计算机指令也是不同的,在不同的操作系统中就要进行不同的编译,写出的代码在不同的操作系统中要进行不同的修改.中间增加了IL层,不管是什么操作系统,

  • 深入理解Java垃圾回收机制以及内存泄漏

    前言 在segmentfault上看到一个问题:java有完善的GC机制,那么在java中是否会出现内存泄漏的问题,以及能否给出一个内存泄漏的案例.本问题视图给出此问题的完整答案. 垃圾回收机制简介 在程序运行过程中,每创建一个对象都会被分配一定的内存用以存储对象数据.如果只是不停的分配内存,那么程序迟早面临内存不足的问题.所以在任何语言中,都会有一个内存回收机制来释放过期对象的内存,以保证内存能够被重复利用. 内存回收机制按照实现角色的不同可以分为两种,一种是程序员手动实现内存的释放(比如C语

  • JVM的垃圾回收机制你了解吗

    目录 一:回收堆内存 1.如何判定对象已死(可达性分析算法) 2.对象的引用级别 3.对象的死亡过程 二:垃圾回收算法 1.标记清除算法 2.标记复制算法 3.标记整理算法 三:垃圾收集器 1.G1(GarbageFirst) 总结 一:回收堆内存 1.如何判定对象已死(可达性分析算法) 当前主流的商用程序语言的内存管理子系统,都是通过可达性分析算法来判定对象是否存活的.这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程

随机推荐