原生内存(堆外内存)

在JVM使用的内存中,通常堆消耗的部分最多,但是JVM也会为内部操作分配一些内部。这些非堆内存就是原生内存。

应用中可以通过JNI的malloc()类似的方法或者是使用NIO的API分配。JVM使用的原生内存和堆内存的总量,就是一个应用总的内存占用(Footprint)。

1、测量内存占用

unix系统中,像top和ps这样的程序可以给出基本数据;

window系统中,可以使用perfmon或VMMap;

何种平台,都需要看看进程实际分配的内存(这与保留的内存完全不同)。分配内存(也叫提交内存),保留内存(有时也叫作虚拟内存)。

分配内存和保留内存区别:

通过示例说明,JVM必须告知操作系统,它的堆可能需要多达2GB的内存,所以会保留这么多内存:操作系统承诺,当JVM因为要增加堆而尝试分配额外的内存时,这些内存时可以获取到的。最初分配的内存时512MB,而且这就是实际用到的全部内存。也即是提交内存。提交内存的量随堆的重新调整而波动;特别是,提交内存会随着堆的增加而相应增加。

线程栈是个例外。JVM每次创建线程时,操作系统分配一些原生内存来保存线程栈,向线程提交更多内存(至少要等到线程退出)。线程栈是在创建时全部分配的。

2、原生NIO缓冲区

开发者 可以通过JNI调用来分配原生内存,但是如果NIO字节缓冲区是通过allocateDirect()方法创建的,则也会分配原生内存。从性能角度看,原生字节缓冲区非常重要,因为它们支持原生代码和Java代码在不复制的情况下共享数据。

 调用allocateDirect()方法非常昂贵,所以应该尽可能重用直接字节缓冲区。理想的情况是,线程是独立的,而且每个线程持有一个直接字节缓存区作为线程局部变量。直接字节缓冲区的对象池可能更有用。

字节缓存区也可以切割管理。应用可以分配一个非常大的直接字节缓冲区,然后每个请求使用ByteBuffer类的slice()方法从中分配一部分。如果每次不能分配相同大小,这种方案也很难处理:会形成碎片。而且不像堆那样还可以对碎片进行压缩,字节缓冲区中的不同片段是无法压缩的,所以只有当所有片段大小都相同时,这种解决方案才好用。

直接字节缓冲区所分配的内存总量:可以通过设置-XX:MaxDirectMemorySize=N标志来指定。从JAVA7开始,这个标志默认值为0,以为没有限制(当然受制于地址空间大小,以及操作系统对进程的各种限制)。

3、原生内存跟踪

从java8开始,借助-XX:NativeMemoryTracking=off|summary|detail这个选项,JVM支持我们一窥它是如何分配原生内存的。原生内存跟踪(Native Memory Tracking,NMT)默认是关闭的(off)。如果开启了概要模式(summary)或详情模式(detail),可以随时通过jcmd命令获得原生内存的信息:

jcmd process_id VM.native_memory summary

如果JVM是使用-XX:+PrintNMTStatistics参数(默认false)启动的,它会在程序退出时打印原生内存分配信息。

示例:

package com.dxz.jvm;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 1堆溢出信息
 * @VM args:-verbose:gc -Xms20M -Xmx20M -XX:+PrintGCDetails
 * 
 * 2原生内存信息打印
 * @VM args:-verbose:gc -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:+UnlockDiagnosticVMOptions  -XX:+PrintNMTStatistics -XX:NativeMemoryTracking=summary
 */
public class HeapOutOfMemory {

    public static void main(String[] args) {
        List cases = new ArrayList();
        HeapOutOfMemory heapOutOfMemory = new HeapOutOfMemory();
        int i = 0;
        while (i <10) {
            cases.add(heapOutOfMemory.new TestObject());
            i++;
        }
        try {
            TimeUnit.SECONDS.sleep(60);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public class TestObject {
        private double a = 34.53;
        private Integer b = 9999999;
    }
}

 

用jcmd

D:\workspace\study\target>jcmd 11532 VM.native_memory summary
11532:

Native Memory Tracking:

Total: reserved=1361449KB, committed=62425KB
-                 Java Heap (reserved=20480KB, committed=20480KB)
                            (mmap: reserved=20480KB, committed=20480KB)

-                     Class (reserved=1062013KB, committed=10109KB)
                            (classes #421)
                            (malloc=5245KB #154)
                            (mmap: reserved=1056768KB, committed=4864KB)

-                    Thread (reserved=15423KB, committed=15423KB)
                            (thread #16)
                            (stack: reserved=15360KB, committed=15360KB)
                            (malloc=45KB #82)
                            (arena=18KB #30)

-                      Code (reserved=249636KB, committed=2572KB)
                            (malloc=36KB #314)
                            (mmap: reserved=249600KB, committed=2536KB)

-                        GC (reserved=6667KB, committed=6611KB)
                            (malloc=5771KB #117)
                            (mmap: reserved=896KB, committed=840KB)

-                  Compiler (reserved=132KB, committed=132KB)
                            (malloc=2KB #22)
                            (arena=131KB #3)

-                  Internal (reserved=5380KB, committed=5380KB)
                            (malloc=5316KB #1365)
                            (mmap: reserved=64KB, committed=64KB)

-                    Symbol (reserved=1495KB, committed=1495KB)
                            (malloc=943KB #111)
                            (arena=552KB #1)

-    Native Memory Tracking (reserved=38KB, committed=38KB)
                            (malloc=3KB #37)
                            (tracking overhead=35KB)

-               Arena Chunk (reserved=185KB, committed=185KB)
                            (malloc=185KB)


D:\workspace\study\target>

 

说明:

Total: reserved=1361449KB(保留内存), committed=62425KB(提交内存)
-                 Java Heap (reserved=20480KB(20MB,同参数设置一致), committed=20480KB)
                            (mmap: reserved=20480KB, committed=20480KB)
堆使用情况:
Java Heap (reserved=20480KB, committed=20480KB)
                            (mmap: reserved=20480KB, committed=20480KB)
保留内存为20MB与设置的相同。

-                     Class (reserved=1062013KB, committed=10109KB)
                            (classes #421)
                            (malloc=5245KB #154)
                            (mmap: reserved=1056768KB, committed=4864KB)
用于保存类的元数据的原生内存。classes #421是实际用于保存程序中的421个类而占用的内存相比,JVM保留的内存要更多。

-                    Thread (reserved=15423KB, committed=15423KB)
                            (thread #16)
                            (stack: reserved=15360KB, committed=15360KB)
                            (malloc=45KB #82)
                            (arena=18KB #30)
thread #16,表示16个线程,分配的总内存有15423KB,平均一个线程是1MB。

-                      Code (reserved=249636KB, committed=2572KB)
                            (malloc=36KB #314)
                            (mmap: reserved=249600KB, committed=2536KB)
JIT的代码缓存:根据上面的类数量来的,所以内存占用不是很多。

-                        GC (reserved=6667KB, committed=6611KB)
                            (malloc=5771KB #117)
                            (mmap: reserved=896KB, committed=840KB)
GC算法的处理锁使用的一些堆外空间。


-                  Compiler (reserved=132KB, committed=132KB)
                            (malloc=2KB #22)
                            (arena=131KB #3)
这个区域是供编译器自身操作使用的,这与生成的代码放在代码缓存中是不同的。

-                    Symbol (reserved=1495KB, committed=1495KB)
                            (malloc=943KB #111)
                            (arena=552KB #1)

保留字符串(Interned String)的引用与符号表引用放在这里。

-    Native Memory Tracking (reserved=38KB, committed=38KB)
                            (malloc=3KB #37)
                            (tracking overhead=35KB)
NMT本身的操作也需要一些空间。


NMT跟踪

NMT也支持跟踪内存分配随时间的变化情况。如果JVM在启动时启用了NMT,可以使用如下命令确定内存的基线使用情况:

jcmd process_id VM.native_memory baseline

原生内存(堆外内存)_第1张图片

4、针对不同操作系统优化JVM

JVM可以利用一些调优选项来优化操作系统内存的使用。

4.1、大页

java支持-XX:+UseLargepages选项。其默认值跟具体的操作系统配置有关。在windows上,必须在操作系统中启动大页。(默认使用常规页)

在linux上,UseLargepages默认不会启用,要支持大页,需要配置一下操作系统。

在Solaris上,不要什么操作系统方面配置,默认启用大页。

linux大页

原生内存(堆外内存)_第2张图片

linux透明页

原生内存(堆外内存)_第3张图片

大页大小

原生内存(堆外内存)_第4张图片

 

5、压缩的oop
oop:ordinary object pointer,即普通对象指针,JVM将其用作对象引用的句柄。

 

你可能感兴趣的:(原生内存(堆外内存))