首页 文章

Java:为什么它使用固定数量的内存?或者它如何管理记忆?

提问于
浏览
11

似乎JVM使用了一些固定数量的内存 . 至少我经常看到参数 -Xmx (对于最大尺寸)和 -Xms (对于初始尺寸)表明这一点 .

我觉得Java应用程序不能很好地处理内存 . 我注意到的一些事情:

  • 甚至一些非常小的示例演示应用程序也会加载大量内存 . 也许这是因为加载了Java库 . 但为什么需要为每个Java实例加载库? (这似乎是因为多个小应用程序线性占用更多内存 . 有关我描述此问题的一些细节,请参阅here . )或者为什么这样做?

  • 像Eclipse这样的大型Java应用程序经常会因一些OutOfMemory异常而崩溃 . 这总是很奇怪,因为我的系统上仍然有足够的内存 . 通常,它们会在运行时消耗越来越多的内存 . 我不确定他们是否有一些内存泄漏,或者这是因为内存池中的碎片 - 我觉得后者就是这种情况 .

  • Java库似乎需要比Qt等类似功能强大的库更多的内存 . 为什么是这样? (比较,启动一些Qt应用程序并查看它们的内存使用情况并启动一些Java应用程序 . )

为什么它不使用像 mallocfree 这样的底层系统技术?或者如果他们不喜欢libc实现,他们可以使用jemalloc(就像在FreeBSD和_1530180中一样),这似乎很好 . 我很确定这会比JVM内存池表现更好 . 而且不仅表现更好,还需要更少的内存,尤其是适用于小型应用 .


另外:有人已经尝试过吗?我会对基于LLVM的Java JIT编译器感兴趣,它只使用 malloc / free 进行内存处理 .

或者这也可能与JVM实现和实现不同?我主要使用Sun JVM .

(另请注意:我不是直接在这里谈论GC . GC只负责计算可删除的对象并初始化内存释放但实际的释放是一个不同的子系统.Afaik,它是一些自己的内存池实现,而不仅仅是对 free 的调用 . )


编辑:一个非常相关的问题:Why does the (Sun) JVM have a fixed upper limit for memory usage?或换句话说:为什么JVM处理内存分配的方式与本机应用程序不同?

7 回答

  • 7

    您需要记住,垃圾收集器不仅仅是收集无法访问的对象 . 它还优化堆空间并跟踪exactly where there is memory available以分配用于创建新对象 .

    立即知道存在空闲内存的位置使得新对象的分配有效地进入年轻代,并且防止需要来回运行到底层OS . 根据Sun的Jon Masamitsu的说法,JIT编译器还优化了JVM层之外的这种分配:

    快速路径分配不会调用JVM来分配对象 . JIT编译器知道如何分配年轻代,并且在线生成用于对象分配的分配代码 . 解释器还知道如何在不调用VM的情况下进行分配 .

    请注意,JVM也竭尽全力尝试获得大的连续内存块,这可能有their own performance benefits(参见"The Cost of Missing the Cache") . 我想调用 malloc (或替代方案)在调用之间提供连续内存的可能性有限,但也许我错过了那些东西 .

    此外,通过维护内存本身,垃圾收集器可以根据使用情况和访问模式进行分配优化 . 现在,我不知道它在多大程度上做到这一点,但鉴于已经注册了太阳_1130189_,我想他们已经做了一些事情 .

    保持分配这些内存块也为Java程序提供了保护 . 由于垃圾收集对程序员来说是隐藏的,因此他们可能会放弃内存而无法返回 . 当然,无论哪种方式,你都可以得到 OutOfMemoryException ,但是每次你完成一个对象时,不必毫不费力地将内存返回给操作系统似乎更合理,因为你已经为自己找到了麻烦 .

    除此之外,我将尝试直接解决您的一些意见:

    通常,他们消耗的内存越来越多运行 .

    假设这不仅仅是程序正在做的事情(无论出于何种原因,也许它有泄漏,也许它必须跟踪越来越多的数据),我想它与免费哈希空间比率有关(Sun / Oracle)JVM设置的默认值 . -XX:MinHeapFreeRatio 的默认值为40%,而 -XX:MaxHeapFreeRatio 为70% . 这意味着只要剩余40%的堆空间,堆就会通过从操作系统声明更多内存来调整大小(假设这不会超过 -Xmx ) . 相反,如果可用空间超过70%,它只会将堆内存释放回操作系统 .

    考虑如果我在Eclipse中运行内存密集型操作会发生什么;例如,分析 . 我的内存消耗量将会上升,同时调整堆的大小(可能是多次) . 一旦我到目前为止,70%的堆是免费的 . 这意味着现在分配了大量未充分利用的空间,JVM无意释放 . 这是一个主要缺点,但您可以通过自定义您的情况的百分比来解决它 . 为了更好地了解这一点,您应该对应用程序进行概要分析,以便查看已使用和已分配的堆空间 . 我个人使用YourKit,但有很多好的选择可供选择 .

    *我不知道这是否是实际上唯一的时间以及如何从操作系统的角度观察到这一点,但是文档说它是“GC之后释放堆的最大百分比以避免收缩”,这似乎暗示了这一点 .

    即使是一些非常小的示例演示应用程序也会加载大量内存 .

    我想这取决于它们是什么类型的应用程序 . 我觉得Java GUI应用程序运行内存很重,但我没有任何证据 . 你有一个我们可以看到的具体例子吗?

    但是为什么需要为每个Java实例加载库?

    那么,如果不创建新的JVM进程,您将如何处理加载多个Java应用程序?过程的隔离是一件好事,这意味着独立加载 . 不过,我并不认为这对于一般的流程来说并不常见 .

    最后一点,您在另一个问题中询问的缓慢启动时间可能来自几个初始堆重新分配,这是为了达到基线应用程序内存要求(由于 -Xms-XX:MinHeapFreeRatio ),具体取决于JVM的默认值 .

  • 2

    Java在虚拟机内运行,它限制了它的许多部分行为 . 请注意术语“虚拟机” . 它实际上就像机器是一个独立的实体一样运行,底层的机器/操作系统只是资源 . -Xmx值定义VM将具有的最大内存量,而-Xms定义应用程序可用的起始内存 .

    VM是二进制系统不可知的产物 - 这是一个用于允许字节代码在任何地方执行的解决方案 . 这类似于模拟器 - 比如旧游戏系统 . 它模仿游戏运行的“机器” .

    您遇到OutOfMemoryException的原因是因为虚拟机已达到-Xmx限制 - 它实际上已耗尽内存 .

    就较小的程序而言,它们通常需要更大比例的内存用于VM . 此外,Java有一个默认的起始-Xmx和-Xms(我不记得它们现在是什么),它总是以它开头 . 当您开始构建和运行“真正的”应用程序时,VM和库的开销变得不那么明显 .

    与QT等相关的记忆论证是正确的,但不是全部 . 虽然它使用的内存比其中一些内存多,但它们是针对特定体系结构编译的 . 自从我使用QT或类似的库以来已经有一段时间了,但我记得内存管理不是非常强大,而且内存泄漏在C / C程序中仍然很常见 . 关于垃圾收集的好处是,它消除了导致内存泄漏的许多常见“问题” . (注意:不是全部 . 在Java中泄漏内存仍然很可能,只是有点困难) .

    希望这有助于消除您可能遇到的一些困惑 .

  • 2

    回答你问题的一部分;

    启动时Java分配内存的“堆”或固定大小的块(-Xms参数) . 它实际上并没有立即使用所有这些内存,但它告诉操作系统“我想要这么多内存” . 然后在你创建时对象并在Java环境中工作,它将创建的对象放入此预分配内存堆中 . 如果该内存块已满,则它将从OS请求更多内存,直到达到“最大堆大小”(-Xmx参数) .

    一旦达到最大大小,Java将不再从操作系统请求更多RAM,即使有很多空闲 . 如果您尝试创建更多对象,则不会留下堆空间,并且您将获得OutOfMemory异常 . 现在,如果您正在查看Windows任务管理器或类似的东西,您将看到使用X兆内存的“java.exe” . 那种排序对应于它为堆请求的内存量,而不是堆内所使用的内存量 .

    换句话说,我可以编写应用程序:

    class myfirstjavaprog
    {  
        public static void main(String args[])
        {
           System.out.println("Hello World!");
        }
    }
    

    这基本上只需要很少的记忆 . 但如果我用cmd行运行它:

    java.exe myfirstjavaprog -Xms 1024M
    

    然后在启动时java将立即向操作系统询问1,024 MB的RAM,这就是Windows任务管理器中显示的内容 . 实际上,该ram没有被使用,但java保留它供以后使用 .

    相反,如果我有一个试图创建10,000字节大数组的应用程序:

    class myfirstjavaprog
    {  
        public static void main(String args[])
        {
           byte[] myArray = new byte[10000];
        }
    }
    

    但是用命令行运行它:

    java.exe myfirstjavaprog -Xms 100 -Xmx 100
    

    然后Java只能分配多达100个字节的内存 . 由于10,000字节的数组不适合100字节的堆,即使操作系统有足够的RAM,也会引发OutOfMemory异常 .

    我希望这是有道理的...


    编辑:

    回到“为什么Java使用如此多的内存”;为什么你认为它使用了大量的内存?如果您正在查看操作系统报告的内容,那么这不是它实际使用的内容,它只是保留使用的内容 . 如果你想知道java实际使用了什么,那么你可以进行堆转储并浏览堆中的每个对象,看看它使用了多少内存 .

    回答“为什么它不让操作系统处理它?”,我想这对于那些设计它的人来说只是一个基本的Java问题 . 我看待它的方式; Java在JVM中运行,JVM是一个虚拟机 . 如果您创建VMWare实例或只是系统的任何其他“虚拟化”,您通常必须指定虚拟系统将/可以消耗多少内存 . 我认为JVM是相似的 . 此外,这种抽象的内存模型让不同操作系统的JVM都以类似的方式运行 . 因此,例如Linux和Windows具有不同的RAM分配模型,但JVM可以将其抽象出来并遵循不同操作系统的相同内存使用情况 .

  • 0

    Java确实使用了 mallocfree ,或者至少可以使用JVM的实现 . 但由于Java跟踪分配和垃圾收集无法访问的对象,因此它们绝对不够 .

    至于你的其他文字,我不确定那里是否有问题 .

  • 3

    即使是一些非常小的示例演示应用程序也会加载大量内存 . 也许这是因为加载了Java库 . 但为什么需要为每个Java实例加载库? (这似乎是因为多个小应用程序线性占用更多内存 . 请参阅此处了解我描述此问题的一些细节 . )或者为什么这样做?

    这可能是由于启动和运行JVM的开销

    像Eclipse这样的大型Java应用程序经常会遇到一些OutOfMemory异常 . 这总是很奇怪,因为我的系统上仍然有足够的内存 . 通常,它们会在运行时消耗越来越多的内存 . 我不确定他们是否有一些内存泄漏,或者这是因为内存池中的碎片 - 我觉得后者就是这种情况 .

    我并不完全确定你的意思是“经常崩溃”,因为我认为这在很长一段时间内都没有发生过 . 如果是,则可能是由于您之前提到的“最大尺寸”设置 .

    你问及为什么Java不使用 mallocfree 的主要问题归结为目标市场问题 . Java旨在消除开发人员对内存管理的头痛 . Java 's garbage collector does a reasonably good job of freeing up memory when it can be freed, but Java isn' t意味着在内存限制的情况下与C竞争 . Java做了它打算做的事情(删除开发人员级内存管理),JVM很好地承担了责任,对大多数应用程序来说都足够好 .

  • 14

    限制是Sun公司经过深思熟虑的设计决定 . 我见过至少另外两个没有这种设计的JVM - 微软的和非pc的非PC AS / 400系统 . 两者都根据需要增长根据需要记忆很多 .

  • 2

    Java不使用固定大小的内存,它总是在-Xms到-Xmx的范围内 .

    如果Eclipse与OutOfMemoryError崩溃,则需要的内存多于-Xmx授予的内存(配置问题) .

    Java不能使用malloc / free(用于创建对象),因为它的内存处理因垃圾收集(GC)而大不相同 . GC会自动删除未使用的对象,与负责内存管理相比,这是一个好处 .

    有关此复杂主题的详细信息,请参阅Tuning Garbage Collection

相关问题