我的编程空间,编程开发者的网络收藏夹
学习永远不晚

JVM堆外内存怎么实现

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

JVM堆外内存怎么实现

这篇“JVM堆外内存怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“JVM堆外内存怎么实现”文章吧。

概述

广义的堆外内存

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

狭义的堆外内存

而作为java开发者,我们常说的堆外内存溢出了,其实是狭义的堆外内存,这个主要是指java.nio.DirectByteBuffer在创建的时候分配内存,我们这篇文章里也主要是讲狭义的堆外内存,因为它和我们平时碰到的问题比较密切

JDK/JVM里DirectByteBuffer的实现

DirectByteBuffer通常用在通信过程中做缓冲池,在mina,netty等nio框架中屡见不鲜,先来看看JDK里的实现:

DirectByteBuffer(int cap) {                   // package-private    super(-1, 0, cap, cap);    boolean pa = VM.isDirectMemoryPageAligned();    int ps = Bits.pageSize();    long size = Math.max(1L, (long)cap + (pa ? ps : 0));    Bits.reserveMemory(size, cap);    long base = 0;    try {        base = unsafe.allocateMemory(size);    } catch (OutOfMemoryError x) {        Bits.unreserveMemory(size, cap);        throw x;    }    unsafe.setMemory(base, size, (byte) 0);    if (pa && (base % ps != 0)) {        // Round up to page boundary        address = base + ps - (base & (ps - 1));    } else {        address = base;    }    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));    att = null;}

通过上面的构造函数我们知道,真正的内存分配是使用的Bits.reserveMemory方法

     static void reserveMemory(long size, int cap) {        synchronized (Bits.class) {            if (!memoryLimitSet && VM.isBooted()) {                maxMemory = VM.maxDirectMemory();                memoryLimitSet = true;            }            // -XX:MaxDirectMemorySize limits the total capacity rather than the            // actual memory usage, which will differ when buffers are page            // aligned.            if (cap <= maxMemory - totalCapacity) {                reservedMemory += size;                totalCapacity += cap;                count++;                return;            }        }        System.gc();        try {            Thread.sleep(100);        } catch (InterruptedException x) {            // Restore interrupt status            Thread.currentThread().interrupt();        }        synchronized (Bits.class) {            if (totalCapacity + cap > maxMemory)                throw new OutOfMemoryError("Direct buffer memory");            reservedMemory += size;            totalCapacity += cap;            count++;        }    }

通过上面的代码我们知道可以通过-XX:MaxDirectMemorySize来指定最大的堆外内存,那么我们首先引入两个问题

  • 堆外内存默认是多大

  • 为什么要主动调用System.gc()

堆外内存默认是多大

如果我们没有通过-XX:MaxDirectMemorySize来指定最大的堆外内存,那么默认的最大堆外内存是多少呢,我们还是通过代码来分析

上面的代码里我们看到调用了sun.misc.VM.maxDirectMemory()

 private static long directMemory = 64 * 1024 * 1024;    // Returns the maximum amount of allocatable direct buffer memory.    // The directMemory variable is initialized during system initialization    // in the saveAndRemoveProperties method.    //    public static long maxDirectMemory() {        return directMemory;    }

看到上面的代码之后是不是误以为默认的最大值是64M?其实不是的,说到这个值得从java.lang.System这个类的初始化说起

     private static void initializeSystemClass() {        // VM might invoke JNU_NewStringPlatform() to set those encoding        // sensitive properties (user.home, user.name, boot.class.path, etc.)        // during "props" initialization, in which it may need access, via        // System.getProperty(), to the related system encoding property that        // have been initialized (put into "props") at early stage of the        // initialization. So make sure the "props" is available at the        // very beginning of the initialization and all system properties to        // be put into it directly.        props = new Properties();        initProperties(props);  // initialized by the VM        // There are certain system configurations that may be controlled by        // VM options such as the maximum amount of direct memory and        // Integer cache size used to support the object identity semantics        // of autoboxing.  Typically, the library will obtain these values        // from the properties set by the VM.  If the properties are for        // internal implementation use only, these properties should be        // removed from the system properties.        //        // See java.lang.Integer.IntegerCache and the        // sun.misc.VM.saveAndRemoveProperties method for example.        //        // Save a private copy of the system properties object that        // can only be accessed by the internal implementation.  Remove        // certain system properties that are not intended for public access.        sun.misc.VM.saveAndRemoveProperties(props);       ......               sun.misc.VM.booted();    }

上面这个方法在jvm启动的时候对System这个类做初始化的时候执行的,因此执行时间非常早,我们看到里面调用了sun.misc.VM.saveAndRemoveProperties(props)

     public static void saveAndRemoveProperties(Properties props) {        if (booted)            throw new IllegalStateException("System initialization has completed");        savedProps.putAll(props);        // Set the maximum amount of direct memory.  This value is controlled        // by the vm option -XX:MaxDirectMemorySize=<size>.        // The maximum amount of allocatable direct buffer memory (in bytes)        // from the system property sun.nio.MaxDirectMemorySize set by the VM.        // The system property will be removed.        String s = (String)props.remove("sun.nio.MaxDirectMemorySize");        if (s != null) {            if (s.equals("-1")) {                // -XX:MaxDirectMemorySize not given, take default                directMemory = Runtime.getRuntime().maxMemory();            } else {                long l = Long.parseLong(s);                if (l > -1)                    directMemory = l;            }        }        // Check if direct buffers should be page aligned        s = (String)props.remove("sun.nio.PageAlignDirectMemory");        if ("true".equals(s))            pageAlignDirectMemory = true;        // Set a boolean to determine whether ClassLoader.loadClass accepts        // array syntax.  This value is controlled by the system property        // "sun.lang.ClassLoader.allowArraySyntax".        s = props.getProperty("sun.lang.ClassLoader.allowArraySyntax");        allowArraySyntax = (s == null                               ? defaultAllowArraySyntax                               : Boolean.parseBoolean(s));        // Remove other private system properties        // used by java.lang.Integer.IntegerCache        props.remove("java.lang.Integer.IntegerCache.high");        // used by java.util.zip.ZipFile        props.remove("sun.zip.disableMemoryMapping");        // used by sun.launcher.LauncherHelper        props.remove("sun.java.launcher.diag");    }

如果我们通过-Dsun.nio.MaxDirectMemorySize指定了这个属性,只要它不等于-1,那效果和加了-XX:MaxDirectMemorySize一样的,如果两个参数都没指定,那么最大堆外内存的值来自于directMemory = Runtime.getRuntime().maxMemory(),这是一个native方法

JNIEXPORT jlong JNICALLJava_java_lang_Runtime_maxMemory(JNIEnv *env, jobject this){    return JVM_MaxMemory();}JVM_ENTRY_NO_ENV(jlong, JVM_MaxMemory(void))  JVMWrapper("JVM_MaxMemory");  size_t n = Universe::heap()->max_capacity();  return convert_size_t_to_jlong(n);JVM_END

其中在我们使用CMS GC的情况下的实现如下,其实是新生代的最大值-一个survivor的大小+老生代的最大值,也就是我们设置的-Xmx的值里除去一个survivor的大小就是默认的堆外内存的大小了

size_t GenCollectedHeap::max_capacity() const {  size_t res = 0;  for (int i = 0; i < _n_gens; i++) {    res += _gens[i]->max_capacity();  }  return res;}size_t DefNewGeneration::max_capacity() const {  const size_t alignment = GenCollectedHeap::heap()->collector_policy()->min_alignment();  const size_t reserved_bytes = reserved().byte_size();  return reserved_bytes - compute_survivor_size(reserved_bytes, alignment);}size_t Generation::max_capacity() const {  return reserved().byte_size();}

为什么要主动调用System.gc

既然要调用System.gc,那肯定是想通过触发一次gc操作来回收堆外内存,不过我想先说的是堆外内存不会对gc造成什么影响(这里的System.gc除外),但是堆外内存的回收其实依赖于我们的gc机制,首先我们要知道在java层面和我们在堆外分配的这块内存关联的只有与之关联的DirectByteBuffer对象了,它记录了这块内存的基地址以及大小,那么既然和gc也有关,那就是gc能通过操作DirectByteBuffer对象来间接操作对应的堆外内存了。DirectByteBuffer对象在创建的时候关联了一个PhantomReference,说到PhantomReference它其实主要是用来跟踪对象何时被回收的,它不能影响gc决策,但是gc过程中如果发现某个对象除了只有PhantomReference引用它之外,并没有其他的地方引用它了,那将会把这个引用放到java.lang.ref.Reference.pending队列里,在gc完毕的时候通知ReferenceHandler这个守护线程去执行一些后置处理,而DirectByteBuffer关联的PhantomReference是PhantomReference的一个子类,在最终的处理里会通过Unsafefree接口来释放DirectByteBuffer对应的堆外内存块

JDK里ReferenceHandler的实现:

 private static class ReferenceHandler extends Thread {        ReferenceHandler(ThreadGroup g, String name) {            super(g, name);        }        public void run() {            for (;;) {                Reference r;                synchronized (lock) {                    if (pending != null) {                        r = pending;                        Reference rn = r.next;                        pending = (rn == r) ? null : rn;                        r.next = r;                    } else {                        try {                            lock.wait();                        } catch (InterruptedException x) { }                        continue;                    }                }                // Fast path for cleaners                if (r instanceof Cleaner) {                    ((Cleaner)r).clean();                    continue;                }                ReferenceQueue q = r.queue;                if (q != ReferenceQueue.NULL) q.enqueue(r);            }        }    }

可见如果pending为空的时候,会通过lock.wait()一直等在那里,其中唤醒的动作是在jvm里做的,当gc完成之后会调用如下的方法VM_GC_Operation::doit_epilogue(),在方法末尾会调用lock的notify操作,至于pending队列什么时候将引用放进去的,其实是在gc的引用处理逻辑中放进去的,针对引用的处理后面可以专门写篇文章来介绍

void VM_GC_Operation::doit_epilogue() {  assert(Thread::current()->is_Java_thread(), "just checking");  // Release the Heap_lock first.  SharedHeap* sh = SharedHeap::heap();  if (sh != NULL) sh->_thread_holds_heap_lock_for_gc = false;  Heap_lock->unlock();  release_and_notify_pending_list_lock();}void VM_GC_Operation::release_and_notify_pending_list_lock() {instanceRefKlass::release_and_notify_pending_list_lock(&_pending_list_basic_lock);}

对于System.gc的实现,之前写了一篇文章来重点介绍,jvm原理之SystemGC源码分析,它会对新生代的老生代都会进行内存回收,这样会比较彻底地回收DirectByteBuffer对象以及他们关联的堆外内存,我们dump内存发现DirectByteBuffer对象本身其实是很小的,但是它后面可能关联了一个非常大的堆外内存,因此我们通常称之为『冰山对象』,我们做ygc的时候会将新生代里的不可达的DirectByteBuffer对象及其堆外内存回收了,但是无法对old里的DirectByteBuffer对象及其堆外内存进行回收,这也是我们通常碰到的最大的问题,如果有大量的DirectByteBuffer对象移到了old,但是又一直没有做cms gc或者full gc,而只进行ygc,那么我们的物理内存可能被慢慢耗光,但是我们还不知道发生了什么,因为heap明明剩余的内存还很多(前提是我们禁用了System.gc)。

为什么要使用堆外内存

DirectByteBuffer在创建的时候会通过Unsafe的native方法来直接使用malloc分配一块内存,这块内存是heap之外的,那么自然也不会对gc造成什么影响(System.gc除外),因为gc耗时的操作主要是操作heap之内的对象,对这块内存的操作也是直接通过Unsafe的native方法来操作的,相当于DirectByteBuffer仅仅是一个壳,还有我们通信过程中如果数据是在Heap里的,最终也还是会copy一份到堆外,然后再进行发送,所以为什么不直接使用堆外内存呢。对于需要频繁操作的内存,并且仅仅是临时存在一会的,都建议使用堆外内存,并且做成缓冲池,不断循环利用这块内存。

为什么不能大面积使用堆外内存

如果我们大面积使用堆外内存并且没有限制,那迟早会导致内存溢出,毕竟程序是跑在一台资源受限的机器上,因为这块内存的回收不是你直接能控制的,当然你可以通过别的一些途径,比如反射,直接使用Unsafe接口等,但是这些务必给你带来了一些烦恼,Java与生俱来的优势被你完全抛弃了&mdash;开发不需要关注内存的回收,由gc算法自动去实现。另外上面的gc机制与堆外内存的关系也说了,如果一直触发不了cms gc或者full gc,那么后果可能很严重。

以上就是关于“JVM堆外内存怎么实现”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注编程网行业资讯频道。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

JVM堆外内存怎么实现

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

JVM堆外内存怎么实现

这篇“JVM堆外内存怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“JVM堆外内存怎么实现”文章吧。概述广义的堆外内存
2023-06-29

jvm怎么设置堆内存参数

要设置JVM的堆内存参数,需要在JVM启动时使用-Xmx和-Xms选项来指定最大堆内存和初始堆内存的大小。-Xmx选项用于指定最大堆内存大小,可以使用以下命令设置为2GB:java -Xmx2g MyApp-Xms选项用于指定初始堆内
2023-10-27

怎么设置jvm堆内存大小

要设置JVM堆内存大小,可以使用以下两个参数:1. -Xms:指定JVM堆的初始大小。2. -Xmx:指定JVM堆的最大大小。这两个参数可以一起设置,也可以分别设置。一般情况下,初始大小和最大大小应该设置为相同的值,以避免JVM在运行过程中
2023-10-10

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

对于有Java开发经验的朋友都知道,Java中不需要手动的申请和释放内存,JVM会自动进行垃圾回收;而使用的内存是由JVM控制的。那么,什么时机会进行垃圾回收,如何避免过度频繁的垃圾回收?如果JVM给的内存不够用,怎么办?此时,堆外内存登场
2023-05-30

JVM中怎么使用jstat查看堆内存

在JVM中使用jstat命令可以查看堆内存的使用情况。具体步骤如下:打开命令行工具,进入到JVM的bin目录下,通常位于JDK的安装目录下。输入以下命令查看当前JVM进程的堆内存使用情况:jstat -gc 其中,为JVM进程的进
JVM中怎么使用jstat查看堆内存
2024-03-05

java堆外内存泄漏怎么排查

在Java中,堆外内存通常是通过直接内存(Direct Memory)分配的。直接内存是一种不受Java堆内存管理的内存分配方式,它是通过调用系统的本地方法进行分配和释放的。当出现堆外内存泄漏时,可以使用以下方法进行排查:检查代码中直接内
2023-10-27

使用JVM怎么实现栈溢出和堆溢出

本篇文章为大家展示了使用JVM怎么实现栈溢出和堆溢出,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。一、栈溢出StackOverflowError栈是线程私有的,生命周期与线程相同,每个方法在执行的时
2023-06-15

JVM内存溢出怎么解决

本篇内容介绍了“JVM内存溢出怎么解决”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!栈溢出(虚拟机栈和本地方法栈)产生原因在HotSpot中
2023-06-22

jvm内存怎么设置合理

设置JVM内存大小需要根据应用程序的需求和服务器的硬件条件来决定。以下是一些设置合理JVM内存的方法:1. 确定最佳的初始堆大小(-Xms参数)和最大堆大小(-Xmx参数)。初始堆大小应该设置为应用程序在启动时所需的最小内存量,最大堆大小应
2023-09-07

怎么组成JVM内存结构

今天就跟大家聊聊有关怎么组成JVM内存结构,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。你对JVM内存结构是否了解,这里和大家分享一下,JVM内存结构主要包括两个子系统和两个组件,这
2023-06-17

linux怎么查看堆内存大小

在Linux系统中,可以使用以下命令来查看堆内存的大小:1. 使用`free`命令:```free -h```这个命令会显示系统的内存使用情况,其中包括堆内存的大小。2. 使用`top`命令:```top```在`top`命令的输出中,可以
2023-08-30

jvm怎么设置栈内存大小

在JVM中,可以使用-Xss参数来设置栈内存大小。例如,要将栈内存大小设置为1MB,可以使用以下命令:java -Xss1m YourClassName在这个命令中,-Xss1m表示将栈内存大小设置为1MB。可以根据需要将1m替换为所
jvm怎么设置栈内存大小
2023-10-28

arthas怎么修改jvm内存对象

要修改 JVM 内存对象,可以通过调整 JVM 的启动参数来修改。具体步骤如下:找到 JVM 的启动脚本,一般是一个以 .sh 或 .bat 结尾的文件。打开这个启动脚本,找到 JVM 的启动参数部分。修改参数 -Xms 和 -Xmx
arthas怎么修改jvm内存对象
2024-03-12

java怎么设置堆内存大小

在Java中,可以使用`-Xmx`参数来设置堆内存的大小。`-Xmx`参数后面跟着要分配的内存大小,单位可以是M(兆字节)或G(千兆字节)。例如,要将堆内存大小设置为2GB,可以使用以下命令:```java -Xmx2G YourClass
2023-09-27

tomcat怎么修改jvm内存大小

要修改Tomcat的JVM内存大小,需要编辑Tomcat的启动脚本文件。以下是在Windows和Linux上修改JVM内存大小的步骤:Windows:1. 在Tomcat的安装目录中,找到bin文件夹。2. 打开catalina.bat文件
2023-09-09

linux怎么设置jvm内存参数

在 Linux 上设置 JVM 内存参数可以通过修改 JVM 的启动脚本或者设置环境变量来实现。以下是两种常见的方法:方法一:修改 JVM 启动脚本1. 找到 JVM 的启动脚本,一般在 Java 的安装目录下的 bin 目录中,文件名为
2023-09-26

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录