今日看到一篇特别好的文章,翻译其间一小段Understanding how the JVM uses native memory

JRE如何运用native存储

Runtime环境提供了被某些未知的用户代码驱动的能力,这使runtime在任何情况下都能运用适宜的资源。每一个JVM管理的java运用的行为都会潜在的影响JVM所能提供的运转时环境。这一节我们讨论为什么Java运用会耗费native存储

Java堆和GC

Java的堆是用来存储分配目标的一块内存,大多数的JVM有一块逻辑堆内存,也有少数的JVM完成了多块堆存储。一个物理内存能够依据GC被分配成多块逻辑上的内存。

The Just-in-time (JIT) compiler

JIT编译器会把java字节码编译成运转时能够直接运转的机器码,这极大的提升了JRE运转速度,使Java代码运转比肩native code。 字节码编译会运用native内存(同理,一些像GCC这样的编译器也需求内存去run),可是JIT的输入(字节码)输出(机器码)都必须存储在native内存中。所以包括许多JIT-compiled的办法的运用相对来说更占用native内存。

Classes and classloaders

Java程序由定义了目标和办法逻辑的类组成,或许是Java运转时的库(比方java.lang.String),也或许是三方库。这些class在被运用的时分会被加载进来并被存储在内存里面。 class如何被存储不同JVM的完成相差极大。Sun JDK存储在永生带(PermGen),IBM从Java5开端为每个classloader拓荒native内存并将它们存储在那里。详细的存储方位需求检查完成的文档。 显而易见的是,用更多的类会耗费更多的内存。(这意味着你的native内存耗费会持续添加,或许清晰的拓荒一块内存,像PermGen,去包容一切的class),需求注意的是不止是你的运用的class需求存储,frameworks,application servers,三方库,JRE这儿面的class在被用到的时分都会被加载并存储进来。 JRE答应卸载class去回收空间,可是这只是是在内存严重不足的情况下。不或许只是卸载一个独自的class文件,而是卸载classloader,和它加载进来的一切class,一个classloader只是会在以下情况下被卸载:

  • Java堆中不包括任何代表此classloader的java.lang.ClassLoader目标的应引证
  • Java堆中不包括任何代表由此classloader加载进来的类的java.lang.Class目标的引证
  • Java堆中没有任何被此classloader加载进来的目标存活。
JNI

JNI答应本地代码和java代码相互调用。JRE严重依靠JNI代码去完成文件和网络这些类库的功用,一个JNI运用能以三种办法添加JRE的native内存

  • JNI运用的native代码会被编译进一个so动态链接库,运转时会被加载到可履行的地址空间呢,大型native运用程序只需加载就可占据进程地址空间的很大一部分。
  • native代码必须跟JVM共享内存,任何native代码分配或许映射所需求的native内存都需求占用JVM的内存。
  • 某些JNI办法能够运用native作为他们正常操作的一部分,比方GetTypeArrayElements或许GetTypeArrayRegion办法都能够拷贝Java堆内存到到native内存供native代码运用。以这种办法访问大块的Java堆内存相应的会占用大量的native内存
NIO

NIO是java1.4之后添加的API,依据管道和缓存,以一种新的办法完成IO操作。除了依据堆的I/O,NIO还添加了依据native内存的direct ByteBuffer(经过java.nio.ByteBuffer.allocateDirect()办法分配)。Direct ByteBuffers能够直接调用体系库的办法去完成I/O操作,这会显示提升在某些场景下的履行效率,因为能防止在Java堆和native堆之间拷贝数据。 我们或许会疑问direct ByteBuffer申请的内存究竟存在哪里,运用依然用的是Java堆里面的目标去完成I/O操作,可是持有数据的缓存依然存在native内存中 -Java堆的目标只是持有了一个native堆缓存的引证。一个non-direct ByteBuffer则是直接在Java堆中存储了byte[]数组。

JVM怎么使用native memory(How the Java runtime uses native memory)

Java堆产生GC的时分相同会对Direct ByteBuffer数据履行清除native缓存操作,GC只是会在Java堆中已经满了,不支持新的堆空间分配或许程序手动调用GC(不建议手动调用GC)的情况下产生。 还有一种情况,native内存已经满了,又有代码来请求native内存,可是这个时分Java堆还没有达到GC的条件,所以并不会产生GC。(也就是说native内存的GC彻底依靠Java堆的GC,反之如果native需求GC了可是堆没有GC的需求的则不会引发GC)

Threads

运用的每一个线程都需求内存去贮存它的栈(这块内存用来存储本地变量表和保存状况),每一个Java线程都需求栈去履行,依据完成,Java线程能够具有独自的native和Java栈。除了仓库空间之外,每个线程还需求一些native内存用于thread-local存储和内部数据结构。 仓库巨细因Java完成和架构而异。某些完成答应您指定Java线程的仓库巨细。通常在256KB和756KB之间的值。 尽管每个线程运用的内存量非常小,但关于具有数百个线程的运用程序,线程仓库的总内存运用量或许很大。运转具有比可用处理器多的线程来运转它们的运用程序通常是低效的,而且或许导致功能低下以及添加的内存运用。