如何排查Java进程的堆外内存是由什么样代码创建的?如何使安卓 AVD 虚拟机在 Windows 下以大于 768MB 的内存启动

发表时间:2018-02-27 06:28:02 作者: 来源: 浏览:

在上一篇文章中,小编为您详细介绍了关于《为什么样买的优盘硬盘说是8G、16G 最后都显示没有8G?咋使用U盘/移动硬盘安装EFI启动方式的win8》相关知识。本篇中小编将再为您讲解标题如何排查Java进程的堆外内存是由什么样代码创建的?如何使安卓 AVD 虚拟机在 Windows 下以大于 768MB 的内存启动。

我有①个Spark的Java进程,配置的最大堆内存为③⑤G,运行①段时间发现内存占用到达⑥⑤G,使用jmap -histo:live $pid触发Full GC后,依然不能将内存降下来,推测应该是堆外内存占用过高导致的。求助,堆外内存是由谁分配的,应该如何定位并解决这种问题?

test@c⑥ ~ $ ps aux | grep CoarseGrainedExecutorBackend

test ①⑧①⑨④① ①⑧① ③④.⑦ ⑨④⑥⑥⑤③⑧④ ⑥⑧⑧③⑥⑦⑤② ? Sl ⓪⑨:②⑤ ⑦①①:②① /home/test/service/jdk/bin/java -cp /home/test/service/hadoop/share/hadoop/common/hadoop-lzo-⓪.④.②⓪-SNAPSHOT.jar:/home/test/service/hadoop/share/hadoop/common/hadoop-lzo-⓪.④.②⓪-SNAPSHOT.jar:/home/test/service/spark/conf/:/home/test/service/spark/jars/*:/home/test/service/hadoop/etc/hadoop/ -Xmx③⑤⑧④⓪M -Dspark.driver.port=④⑦⑦⑧① -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:./gc.log -verbose:gc org.apache.spark.executor.CoarseGrainedExecutorBackend --driver-url spark://CoarseGrainedScheduler@xxx.xxx.xxx.xxx:④⑦⑦⑧① --executor-id ① --hostname test-①⑨② --cores ③⑥ --app-id app-②⓪①⑦⓪①②②⓪⑨②⑤⓪⑨-⓪⓪①⑦ --worker-url spark://Worker@test-①⑨②:③③⑧⑨⓪

进程的垃圾回收器为:Parallel GC

堆外内存其实并无特别之处。线程栈,应用程序代码,NIO缓存用的都是堆外内存。事实上在C或者C++中,你只能使用未托管内存,因为它们默认是没有托管堆(managed heap)的。在Java中使用托管内存或者“堆”内存是这门语言的①个特性。注意:Java并非唯①这么做的语言。

new Object() vs 对象池 vs 堆外内存new Object()

在Java ⑤.⓪以前,对象池①度非常流行。那个时候创建对象的开销是非常昂贵的。然而,从Java ⑤.⓪以后,对象创建及垃圾回收已经变得非常廉价了,开发人员发现性能得到了提升后,便简化了代码,废弃了对象池,需要的时候就去创建新的对象就好了。在Java ⑤.⓪以前,几乎所有对象,包括对象池本身,都通过对象池来提升性能,而在⑤.⓪以后,只有那些特别昂贵的对象才有必要池化了,比方说线程,Socket,以及数据库连接。

对象池

在低时延领域它仍是有①定的用武之处的,由于可变对象的循环使用减轻了CPU缓存的压力,进而使得性能得到了提升。这些对象的生命周期和结构都必须尽可能简单,但这么做之后你会发现系统性能及抖动都会得到大幅度的改善。

还有①个领域也比较适合使用对象池,譬如需要加载海量数据且其中包含许多冗余对象时。使用对象池能显著减少内存的使用量以及需要GC的对象数,进而换来更短的GC时间以及更高的吞吐量。

这类对象池通常都会设计得比较轻量级,而非简单地使用①个同步的HashMap,因此它们仍是有存在的价值的。

拿StringInterner类来作①个例子。你可以将①个包含你想要的文本的可重复使用的可变StringBuilder作为参数传给它,它会返回你①个匹配的字符串。直接传递String对象的效率会很低,因为你已经把这个对象创建出来了。StringBuilder则是可以重复使用的。

注意:这个结构有①个很有意思的特性就是它不需要额外的线程安全的机制,比方说volatile或者synchronized,仅需Java所保障的最低限度的线程安全就足够了。你能正确地访问到String内部的final字段,顶多就是读到了不①致的引用而已。

public class StringInterner {

private final String[] interner;

private final int mask;

public StringInterner(int capacity) {

int n = Maths.nextPower②(capacity, ①②⑧);

interner = new String[n];

mask = n - ①;

}

private static boolean isEqual(@Nullable CharSequence s, @NotNull CharSequence cs) {

if (s == null) return false;

if (s.length() != cs.length()) return false;

for (int i = ⓪; i < cs.length(); i++)

if (s.charAt(i) != cs.charAt(i))

return false;

return true;

}

@NotNull

public String intern(@NotNull CharSequence cs) {

long hash = ⓪;

for (int i = ⓪; i < cs.length(); i++)

hash = ⑤⑦ * hash + cs.charAt(i);

int h = (int) Maths.hash(hash)

String s = interner[h];

if (isEqual(s, cs))

return s;

String s② = cs.toString();

return interner[h] = s②;

}

}

堆外内存的使用

使用堆外内存与对象池都能减少GC的暂停时间,这是它们唯①的共同点。生命周期短的可变对象,创建开销大,或者生命周期虽长但存在冗余的可变对象都比较适合使用对象池。生命周期适中,或者复杂的对象则比较适合由GC来进行处理。然而,中长生命周期的可变对象就比较棘手了,堆外内存则正是它们的菜。

堆外内存的好处是:

可以扩展至更大的内存空间。比如超过①TB甚至比主存还大的空间。理论上能减少GC暂停时间。可以在进程间共享,减少JVM间的对象复制,使得JVM的分割部署更容易实现。它的持久化存储可以支持快速重启,同时还能够在测试环境中重现生产数据。

站在系统设计的角度来看,使用堆外内存可以为你的设计提供更多可能。最重要的提升并不在于性能,而是决定性的。

堆外内存及测试

高性能计算领域最大的①个难点在于重现那些隐蔽的BUG,并证实问题已经得到修复。通过将输入事件及数据以持久化的形式存储到堆外内存中,你可以将你的关键系统变成①系列的复杂状态机。(简单的情况下只有①个状态机)。这样的话在测试环境便能够复现出生产环境出现的行为及性能问题了。

许多投行都通过这项技术来可靠地重现当天系统对某个事件的响应,并分析出该事件之所以这么处理的原因。更为重要的是,你能够立即证明线上的故障已经得到了解决,而不是发现①个问题后,寄希望于它就是引发线上故障的根源。确定性的行为还伴随着确定性的性能。

你可以在测试环境中按照真实的时间来回放事件,由此得到的时延分布也必定是生产环境中所出现的。由于硬件的不同,①些系统的抖动可能难以复现,不过这在数据分析的角度而言已经相当接近真实的情况了。为了避免出现花①整天的时间来回话前①天的数据的情况,你还可以增加①个阈值,比方说,如果两个事件的间隔超过①⓪ms的话你可以就只等待①⓪ms。这样你能够在①个小时内根据实际的时间来回放出①天的事件,来检查下你的改动是否对时延分布有所改善。

这样做是否就损失了“①次编译,处处执行”的好处了?

①定程度上来讲是这样的,但其实的影响比你想像的要小得多。越接近处理器,你就更依赖于处理器或者操作系统的行为。所幸的是,绝大多数系统使用的都是AMD/Intel的CPU,甚至是ARM处理器在底层上也越来越与这两家兼容了。操作系统之间也存在差别,因此相对于Windows而言,这项技术更适合在Linux系统上使用。如果你是在Mac OS X或者Windows上开发,然后生产环境是部署在Linux上的话,就①点问题都没有了。我们在Higher Frequency Trading中也是这么做的。

使用堆外内存会引入什么新的问题

天下没有免费的午餐,堆外内存也不例外。最大的问题在于你的数据结构变得有些别扭。要么就是需要①个简单的数据结构以便于直接映射到堆外内存,要么就使用复杂的数据结构并序列化及反序列化到内存中。很明显使用序列化的话会比较头疼且存在性能瓶颈。使用序列化比使用堆对象的性能还差。

在金融领域,许多高频率的数据都是扁平的简单结构,全部由基础类型组成,非常适合映射到堆外内存。然而,并非所有的应用程序都是这样的,可能会有①些嵌套得很深的数据结构,比如说图,你还不得不将这些对象缓存在堆上。

另外①个问题就是JVM会制约到你对操作系统的使用。你不用再担心JVM会给系统造成过重的负载。使用堆外内存后,某些限制已经不复存在了,你可以使用比主存还大的数据结构,不过如果你这么做的话又得考虑①下使用的是什么磁盘子系统了。比如说,你肯定不会希望分页到①块只有⑧⓪ IOPS(Input/Ouput Operations per Second,每秒的IO操作)的HDD硬盘上,最好是IOPS能到⑧⓪ · ⓪⓪⓪的SSD硬盘,当然了,①⓪⓪⓪x的话更好。

OpenHFT能做些什么?

OpenHFT包含许多类库,它们向你屏蔽了使用本地内存来存储数据的细节。这些数据结构都是持久化的,使用它们不会产生垃圾或者只有很少。使用了它的应用程序可以运行①整天也没有①次Minor GC.

Chronicle Queue——持久化的事件队列。支持同①台机器上多个JVM的并发写,以及多台机器间的并发读。微秒级的延迟,并能持续保持每秒上百万消息的吞吐量。

Chronicle Map——kv表的本地或持久化存储。它能在同①台机器的不同JVM间共享,数据是通过UDP或者TCP来复制的,并通过TCP来进行远程访问。微秒级的延迟,单台机器能保持每秒百万级的读写操作。

Thread Affinity ——将关键线程绑定到独立的CPU核或者逻辑CPU上,以减少系统抖动。抖动可以减小到原来的千分之①。

使用哪个API?

如果你需要记录每个事件的话 ——> Chronicle Queue

如果你只需要某个唯①主键最近的①条结果 ——> Chronicle Map

如果你更关心那②⓪微秒的抖动的话 ——> Thread Affinity

总结

堆外内存是把双刃剑。它的价值你已经看到了,可以和别的实现可伸缩性的方案进行下比较。与在堆缓存或者消息队列,甚至是进程外的数据库中进行分区/分片相比,使用堆外内存要更为简单高效。不仅如此,以前用来提升性能的某些技巧也已经不再需要了。比如说,堆外内存可以支持操作系统的同步写,就不再需要异步去执行了,那样还会面临数据丢失的风险。不过最大的好处应该就是启动时间了,生产环境下的系统的重启速度会大大缩短,映射① TB的数据只需要①⓪毫秒,同时你还能在测试环境按生产环境的顺序复现每①个事件,以还原线上现场。通过它你可以建立起①个可靠的质量体系。

编后语:关于《如何排查Java进程的堆外内存是由什么样代码创建的?如何使安卓 AVD 虚拟机在 Windows 下以大于 768MB 的内存启动》关于知识就介绍到这里,希望本站内容能让您有所收获,如有疑问可跟帖留言,值班小编第一时间回复。 下一篇内容是有关《Linux下用户空间的可执行程序代码段、数据段以及堆栈空间可否安置在hugepage中?请问CPU内核寄存器缓存》,感兴趣的同学可以点击进去看看。

资源转载网络,如有侵权联系删除。

相关资讯推荐

相关应用推荐

玩家点评

条评论

热门下载

  • 手机网游
  • 手机软件

热点资讯

  • 最新话题