云服务器价格_云数据库_云主机【优惠】最新活动-搜集站云资讯

全站加速_阿里云uid_多少钱

小七 141 0

无法初始化倭黑猩猩

来自经典的电脑错误haikus:内存不足。我们希望拥有整个天空,但我们永远不会。具体地说,你的应用程序"希望保持整个天空",它间歇性地失败,因为它耗尽了内存。你找不到问题,你的客户越来越不耐烦了。你如何化解这种局面?我写这篇文章的灵感来自于我们最近修复的一个bug,我们的核心基础架构团队致力于解决一个涉及Java堆、垃圾收集器、Java本机访问(JNA)、本机内存分配,甚至是segfaults的问题!这是一个尝试详细说明所有的调试工具和技术,我们使用的根本原因OutOfMemoryErrors,以及一些修复它们的方法。我的希望是,出版我们关于OutOfMemoryErrors的行动手册,将帮助其他人解决棘手的Java内存分配问题,而不是像我们今天所做的那样努力和发现。虽然本文是专门针对在Unix操作系统上运行的oraclejvmforjava7编写的,但是我描述的大多数工具在其他JVM和os中都是可用的或者有1:1的替代品。高级概念和技术甚至适用于不在JVM上运行的垃圾收集语言,但是您需要做更多的心理翻译。揭开Java内存管理的神秘面纱首先,重要的是要模糊地理解JVM是如何管理内存的。在普通的单线程C程序中,内存映射如下所示:但是,在Java中有点不同。每个人都知道Java是垃圾收集的,可能很多人都认为Java内存分配器和垃圾收集器使用malloc()和free()来管理用于存储Java对象的内存。事实并非如此,为了弄清实际情况,让我们看看JDK的一个标准内存调试工具的输出(我删除了一些输出行,并加粗了其他行,以保持对有趣内容的关注):如您所见,Java使用了几个不同的堆"空间"。Java对象存储在"年轻一代"("Eden"/"From"/"To"空格的组合)和"老一代"中;"Perm[anent]Generation"是加载.class文件的地方。通常,每个空间都有一个(有时是多个)与之关联的本机内存区域,并且没有一个空间驻留在本机堆中(malloc()从中获取内存)。JVM使用多个空间的原因与它的垃圾收集器实现有关。因为对象的生存期往往很短(堆栈变量、作为用户输入传递的对象或从数据库临时读入内存的对象等,通常只持续几秒钟)或非常长(单个对象、缓存等,持续应用程序的整个生存期),垃圾回收器经常检查短期对象,仅偶尔检查长寿对象。当你说"Object o=new Object()"时,这个对象最初驻留在年轻一代的Eden空间中,如果它寿命很短,它将被垃圾回收而不离开这个空间。但是,如果它在Eden空间中存活了几个收集周期,它将在往返空间中移动,并最终移动到老一代,这意味着垃圾收集器认为它不会很快成为垃圾。值得注意的是,无法保证垃圾回收器的运行频率或运行时间,而且很常见的是,"完整"的垃圾回收(遍历所有对象的垃圾收集)根本不会发生,除非年轻一代填满了大部分。在上面的jmap输出中甚至没有列出本机堆,但是JVM使用它来存储内部状态,例如实时编译的代码、有关程序状态或字节码解释器的信息等。在大多数UNIX操作系统上,您可以使用pmap查看JVM地址空间(包括堆)中的所有内存区域:与只能用于存储使用"new"分配的Java对象的Java堆空间不同,本机内存是由许多不同的用户分配的,有些用户是受信任的,而另一些用户则不那么信任。最值得信赖的是JVM本身——每当它需要内存来运行一些Java代码时,内存将来自本机堆。本机内存的另一个普通用户更令人惊讶:一些Java的标准库出于性能原因使用本机实现。具体来说,这适用于一些加密和压缩函数以及java.nio软件ByteBuffer对象。这两个本机内存用户表现得非常好:他们经过了良好的测试,很少泄漏内存,并且在无法分配足够的内存时抛出Java异常。还有一些不太可信的本机内存分配器:第三方本机代码。如果您通过JNA或JNI与本机库接口(例如,我们使用JNA调用一些DelphixOS特定的库来进行文件系统和操作系统管理),那么JNA/JNI和本机库本身都将使用本机内存。附加到JVM的Java代理也以本机方式运行并使用本机内存。值得注意的是,所有这些对JVM的本机第三方添加也能够绕过本机堆,通过分配新的内存区域、mmap'ing文件等直接操作JVM的内存映射。这可能会使事情变得一团糟,所以您应该小心只使用符合高质量标准的本机库和Java代理。修复明显的OutOfMemoryError案例如果你和我一样,现在你很想了解OutOfMemoryErrors以及如何调试它们。当你的记忆耗尽时,可能有很多不同的原因。让我们先看看简单的,以及您将在日志或stdout/stderr上看到它们生成的消息。java.lang.OutOfMemoryError:请求的数组大小超过VM限制如果你明白了这一点,那就意味着你刚刚试图分配一个数组,这个数组太大,会消耗掉所有的Java堆空间,所以在99.9%的情况下,这是代码中的一个错误,你错误地计算了要分配的数组大小。在不太可能的情况下,它不是一个bug,您需要通过向JVM的-Xmx参数传递一个更高的值来增加Java堆的最大大小(可能还需要购买更多的RAM)。java.lang.OutOfMemoryError:永久空间如上所述,永久生成是将带有Java字节码的.class文件存储在内存中的位置。大约98%的情况下,这个错误非常简单:如果每次启动程序时它都很快耗尽了PermGen空间,这意味着您用于项目的代码变得太大了,您需要使用-XX:MaxPermSize=<;size>;JVM参数来增加PermGen的最大大小以腾出空间。为了给您一个合理的限制,我们的PermGen大小大约是82MB,对于一个Java代码库,可能有50万行代码和几十个.jar库,我们将PermGen的安全距离设置为512MB,以避免造成麻烦,但仍能捕获异常严重的PermGen空间占用。但是,如果您在Java启动后很长时间内遇到PermGen问题,这通常意味着您的代码或您正在使用的某个第三方库正在动态生成和运行Java代码。例如,在几年前我们切换到PostgreSQL之前,我们使用了一个名为Derby的Java SQL数据库来存储我们的应用程序元数据,它会动态编译代码来执行我们请求的每个新查询。(当我们在一个客户网站的PermGen空间用完时,我了解到了这个功能。解决这样的问题更具体到应用程序,但一般来说,处理它的最佳方法是限制生成的代码量。在最坏的情况下,你也可以通过增加限制来应对它。如果您不确定您的错误属于哪种情况,或者您不知道是什么库在生成代码并导致PermGen增长,那么用于分析Java堆问题的图形化VisualVM工具将是您最好的朋友。它允许您做很多事情,包括按对象的类型分解PermGen内容,或者由装载它们的类加载器分解PermGen中的.class文件。(如果在VisualVM中尝试打开一个大的Java转储时出现OutOfMemoryError,可以调整VisualVM选项(在macosx上,这个文件位于/Applications中)/VisualVM.app/Contents/Resources/VisualVM/etc/VisualVM.conf)并使用以下行增加最大内存限制:另一个比较简单的电影制作工具是Permap,这是一个比较简单的电影制作工具请注意,在对核心文件进行事后分析时,此工具的工作效果要好一些(上面所示的实时分析通常不是超精确的)。java.lang.OutOfMemoryError:无法创建新的本机线程从消息中,您可能已经猜到这与本机内存有关——具体地说,线程需要本机内存才能驻留在与堆分离的堆栈中。这个内存在pmap输出中显示为"[stack tid=。。。]". 根据所使用的操作系统和默认堆栈大小的不同,线程占用不同数量的本机内存,因此线程数量的限制变化很大,但通常为数千个。但是,有一点是与平台无关的:使用这么多线程几乎总是Java代码中的一个bug。在我们的堆栈中,我们通常有300-500个线程,但是这个数量在过去几年中没有增长,所以我认为它已经变得相当稳定了。解决这个限制的方法包括使用线程池、期货、SelectableChannels、WatchService等将相同数量的工作分配到较小数量的线程上。如果您不确定所有线程都在做什么,并且需要弄清楚它来决定如何整合线程的使用,那么可以尝试使用jstack工具,它可以为您提供JVM中每个正在运行的线程的回溯:描述堆耗尽的特征现在我们已经了解了特殊情况,现在是时候解决真正的堆耗尽问题了。这是OutOfMemoryError问题中最广泛的一类,因此也是其中之一