精华内容
下载资源
问答
  • 堆外内存

    2019-07-12 17:04:54
    与之相对的是堆外内存,存在于JVM管控之外的内存区域,Java中对堆外内存的操作,依赖于Unsafe提供的操作堆外内存的native(底层使用c,c++,汇编)方法。 使用堆外内存的原因 对垃圾回收停顿的改善。由于堆外内存是...

    在Java中创建的对象都处于堆内内存(heap)中,堆内内存是由JVM所管控的Java进程内存,并且它们遵循JVM的内存管理机制,JVM会采用垃圾回收机制统一管理堆内存。与之相对的是堆外内存,存在于JVM管控之外的内存区域,Java中对堆外内存的操作,依赖于Unsafe提供的操作堆外内存的native(底层使用c,c++,汇编)方法。

    使用堆外内存的原因

    • 对垃圾回收停顿的改善。由于堆外内存是直接受操作系统管理而不是JVM,所以当我们使用堆外内存时,即可保持较小的堆内内存规模。从而在GC时减少回收停顿对于应用的影响。
    • 提升程序I/O操作的性能。通常在I/O通信过程中,会存在堆内内存到堆外内存的数据拷贝操作,对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到堆外内存。

    DirectByteBuffer是Java用于实现堆外内存的一个重要类,通常用在通信过程中做缓冲池,如在Netty、MINA等NIO框架中应用广泛。DirectByteBuffer对于堆外内存的创建、使用、销毁等逻辑均由Unsafe提供的堆外内存API来实现。

     

    那么如何通过构建垃圾回收追踪对象Cleaner实现堆外内存释放呢?

    Cleaner继承自Java四大引用类型之一的虚引用PhantomReference(众所周知,无法通过虚引用获取与之关联的对象实例,且当对象仅被虚引用引用时,在任何发生GC的时候,其均可被回收),通常PhantomReference与引用队列ReferenceQueue结合使用,可以实现虚引用关联对象被垃圾回收时能够进行系统通知、资源清理等功能。如下图所示,当某个被Cleaner引用的对象将被回收时,JVM垃圾收集器会将此对象的引用放入到对象引用中的pending链表中,等待Reference-Handler进行相关处理。其中,Reference-Handler为一个拥有最高优先级的守护线程,会循环不断的处理pending链表中的对象引用,执行Cleaner的clean方法进行相关清理工作。

    所以当DirectByteBuffer仅被Cleaner引用(即为虚引用)时,其可以在任意GC时段被回收。当DirectByteBuffer实例对象被回收时,在Reference-Handler线程操作中,会调用Cleaner的clean方法根据创建Cleaner时传入的Deallocator来进行堆外内存的释放。 

    转载自:https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html

    展开全文
  • 堆外内存泄漏排查

    万次阅读 2020-08-21 14:42:39
    堆外内存泄漏排查 直接内存:指的是Java应用程序通过直接方式从操作系统中申请的内存,也叫堆外内存,因为这些对象分配在Java虚拟机的堆(严格来说,应该是JVM的内存外,但是堆是这块内存中最大的)以外。 直接内存...

    堆外内存泄漏排查

    直接内存:指的是Java应用程序通过直接方式从操作系统中申请的内存,也叫堆外内存,因为这些对象分配在Java虚拟机的堆(严格来说,应该是JVM的内存外,但是堆是这块内存中最大的)以外。

    直接内存有哪些?

    • 元空间。
    • BIO中ByteBuffer分配的直接内存。
    • 使用Java的Unsafe类做一些分配本地内存的操作。
    • JNI或者JNA程序,直接操纵了本地内存,比如一些加密库、压缩解压等。

    JNI(Java Native Interface):通过使用Java本地接口(C或者C++)书写程序,可以确保代码在不同的平台上方便移植。

    JNA(Java Native Access):提供一组Java工具类用于在运行期间动态访问系统本地库(native library:如 Window 的 dll)而不需要编写任何Native/JNI代码。开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射。

    JNA是建立在JNI技术基础之上的一个Java类库,它使您可以方便地使用java直接访问动态链接库中的函数。原来使用JNI,你必须手工用C写一个动态链接库,在C语言中映射Java的数据类型。而在JNA中,它提供了一个动态的C语言编写的转发器,可以自动实现Java和C的数据类型映射,你不再需要编写C动态链接库。也许这也意味着,使用JNA技术比使用JNI技术调用动态链接库会有些微的性能损失。但总体影响不大,因为JNA也避免了JNI的一些平台配置的开销。

    直接内存的优缺点

    直接内存,其实就是不受JVM控制的内存。相比于堆内存有几个优势:

    • 减少了垃圾回收的工作,因为垃圾回收会暂停其他的工作,能保持一个较小的堆内内存,以减少垃圾收集对应用的影响。
    • 加快了复制的速度。因为堆内在flush到远程时,会先复制到直接内存(非堆内存),然后再发送,而堆外内存相当于省略掉了这个工作。
    • 可以在进程间共享,减少JVM间的对象复制,使得JVM的分割部署更容易实现。
    • 可以扩展至更大的内存空间,比如超过1TB甚至比主存还大的空间。

    直接内存有很多好处,我们还是应该要了解它的缺点:

    • 堆外内存难以控制,如果内存泄漏,那么很难排查。
    • 堆外内存相对来说,不适合存储很复杂的对象,一般简单的对象比较适合。

    直接内存的泄漏

    ByteBuffer

    public class DirectMemoryOOM {
    	
    	public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
    		ByteBuffer.allocateDirect(128 * 1024 * 1024);
    	}
    }
    

    运行时带上JVM参数-XX:MaxDirectMemorySize=100m会抛出如下异常:

    Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
    	at java.nio.Bits.reserveMemory(Bits.java:694)
    	at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
    	at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
    	at com.morris.jvm.heapout.DirectMemoryOOM.main(DirectMemoryOOM.java:14)
    

    如果我们没有通过-XX:MaxDirectMemorySize来指定最大的直接内存,那么默认的最大堆外内存是多少呢?一般来说,如果没有显示的设置-XX:MaxDirectMemorySize参数,通过ByteBuffer能够分配的直接内存空间大小就是堆的最大大小,也就是对应对应参数-Xmx,真的是这么样吗?

    1. VM参数配置:-XX:MaxDirectMemorySize=128m,程序会正常退出。
    2. VM参数配置:-Xmx128m,运行结果为OutOfMemoryError,这就说明了ByteBuffer能够分配的直接内存空间大小并不是-Xmx指定的大小。
    3. VM参数配置:-Xmx135m -Xmn100m -XX:SurvivorRatio=8,运行结果还是OutOfMemoryError。
    4. VM参数配置:-Xmx138m -Xmn100m -XX:SurvivorRatio=8,程序会正常退出。

    总结:没有显示的设置-XX:MaxDirectMemorySize参数,通过ByteBuffer能够分配的直接内存空间大小就是堆的最大可使用的大小。堆的最大可使用的大小=堆的最大值(-Xmx)- 一个Survivor的大小(浪费的空间),所以案例3会OOM,堆的最大的可使用的大小=135m-10m=125m,不能分配128M的对象,而在案例4中,堆的最大可使用的大小=138m-10m=128m ,刚好可以分配128M的对象,所有不会OOM。

    UnSafe

    public class UnSafeDemo {
        public static final int _1MB = 1024 * 1024;
    
        public static void main(String[] args) throws Exception {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            Unsafe unsafe = (Unsafe) field.get(null);
            unsafe.allocateMemory(100 * 1024 * 1024);
        }
    }
    

    运行时加上-XX:MaxDirectMemorySize=10m参数,发现程序并没有OOM,也就是说明使用UnSafe API分配的内存不受-XX:MaxDirectMemorySize参数的控制。

    JNI

    package com.morris.jvm.heapout;
    
    import com.sun.management.OperatingSystemMXBean;
    import com.sun.net.httpserver.HttpContext;
    import com.sun.net.httpserver.HttpServer;
    
    import java.io.*;
    import java.lang.management.ManagementFactory;
    import java.net.InetSocketAddress;
    import java.util.Random;
    import java.util.concurrent.ThreadLocalRandom;
    import java.util.zip.GZIPInputStream;
    import java.util.zip.GZIPOutputStream;
    
    public class LeakDemo {
    
        /**
         * 构造随机的字符串
         */
        public static String randomString(int strLength) {
            Random rnd = ThreadLocalRandom.current();
            StringBuilder ret = new StringBuilder();
            for (int i = 0; i < strLength; i++) {
                boolean isChar = (rnd.nextInt(2) % 2 == 0);
                if (isChar) {
                    int choice = rnd.nextInt(2) % 2 == 0 ? 65 : 97;
                    ret.append((char) (choice + rnd.nextInt(26)));
                } else {
                    ret.append(rnd.nextInt(10));
                }
            }
            return ret.toString();
        }
    
        //复制方法
        public static int copy(InputStream input, OutputStream output) throws IOException {
            long count = copyLarge(input, output);
            return count > 2147483647L ? -1 : (int) count;
        }
    
        //复制方法
        public static long copyLarge(InputStream input, OutputStream output) throws IOException {
            byte[] buffer = new byte[4096];
            long count = 0L;
    
            int n;
            for (; -1 != (n = input.read(buffer)); count += (long) n) {
                output.write(buffer, 0, n);
            }
    
            return count;
        }
    
        //解压
        public static String decompress(byte[] input) throws Exception {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            copy(new GZIPInputStream(new ByteArrayInputStream(input)), out);
            return new String(out.toByteArray());
        }
    
        //压缩
        public static byte[] compress(String str) throws Exception {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            GZIPOutputStream gzip = new GZIPOutputStream(bos);
            try {
                gzip.write(str.getBytes());
                gzip.finish();
                byte[] b = bos.toByteArray();
                return b;
            } finally {
                try {
                    gzip.close();
                } catch (Exception ex) {
                }
                try {
                    bos.close();
                } catch (Exception ex) {
                }
            }
        }
    
        private static OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
    
        //通过MXbean来判断获取内存使用率(系统)
        public static int memoryLoad() {
            double totalvirtualMemory = osmxb.getTotalPhysicalMemorySize();
            double freePhysicalMemorySize = osmxb.getFreePhysicalMemorySize();
    
            double value = freePhysicalMemorySize / totalvirtualMemory;
            int percentMemoryLoad = (int) ((1 - value) * 100);
            return percentMemoryLoad;
        }
    
    
        private static volatile int RADIO = 60;
    
        public static void main(String[] args) throws Exception {
    
            //构造1kb的随机字符串
            int BLOCK_SIZE = 1024;
            String str = randomString(BLOCK_SIZE / Byte.SIZE);
            //字符串进行压缩
            byte[] bytes = compress(str);
            for (; ; ) {
                int percent = memoryLoad();
                if (percent > RADIO) { // 如果操作系统内存使用率达到阈值,则等待1s
                    System.out.println("memory used >" + RADIO + "  hold 1s");
                    Thread.sleep(1000);
                } else {
                    //不断对字符串进行解压
                    decompress(bytes);
                    Thread.sleep(1);
                }
            }
        }
    }
    

    演示程序的简单说明:程序将会申请1kb的随机字符串,然后持续解压。为了避免让操作系统陷入假死状态,我们每次都会判断操作系统内存使用率,在达到60%的时候,我们将挂起程序,这样方便用工具来分析。

    启动参数:java -XX:+PrintGC -Xmx1G -Xmn1G -XX:+AlwaysPreTouch -XX:MaxMetaspaceSize=10M -XX:MaxDirectMemorySize=10M LeakDemo

    参数说明:

    • -XX:+PrintGC:打印GC日志。
    • -Xmx1G:限制堆的最大值为1G。
    • -Xmn1G:限制堆的最小值为1G。
    • -XX:MaxMetaspaceSize=10M:限制元空间的最大值为10M。
    • -XX:MaxDirectMemorySize=10M:限制直接内存的最大值为10M。
    • -XX:+AlwaysPreTouch:如果不设置这个参数,JVM的内存只有真正在使用的时候,才会分配给它。如果设置这个参数,在JVM启动的时候,就把它所有的内存在操作系统分配了。在堆比较大的时候,会加大启动时间,但在这个场景中,我们为了减少内存动态分配的影响,把这个参数添加上。

    这个程序很快就打印了以下的显示,这个证明操作系统内存使用率,达到了60%。

    Java HotSpot(TM) 64-Bit Server VM warning: MaxNewSize (1048576k) is equal to or greater than the entire heap (1048576k).  A new max generation size of 1048064k will be used.
    memory used >80  hold 1s
    memory used >80  hold 1s
    memory used >80  hold 1s
    ... ...
    

    下面通过一些命令和工具分析内存泄漏的原因。

    top

    先使用top命令查看哪个进程的内存占用过高:

    # top
    top - 11:14:34 up 277 days, 50 min,  2 users,  load average: 0.00, 0.00, 0.00
    Tasks: 143 total,   1 running, 141 sleeping,   1 stopped,   0 zombie
    Cpu(s):  0.3%us,  0.3%sy,  0.0%ni, 99.3%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
    Mem:   3921488k total,  2458504k used,  1462984k free,   327152k buffers
    Swap:  4063228k total,    58672k used,  4004556k free,   434392k cached
    
      PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                                                                                                                         
    17238 root      20   0 4287m 1.3g  11m S  7.9 34.9   0:02.69 java                                                                                                                                                                                                             
        1 root      20   0 19360 1032  792 S  0.0  0.0   0:05.59 init   
    

    发现一个进程号为17238的java进程占用内存较高。

    top命令中几个指标说明:

    • VIRT(virtual memory usage):虚拟内存,进程申请的虚拟内存大小,包括进程使用的库、代码、数据等,假如进程申请100m的内存,但实际只使用了10m,那么它会增长100m,而不是实际的使用量。
    • RES(resident memory usage):常驻内存(物理内存),如果申请100m的内存,实际使用10m,它只增长10m。
    • %MEM:进程使用的物理内存占操作系统物理内存的百分比。

    如果进程较多,可以使用top -p 17238命令,只观察这一个进程。

    # top -p 17238
    top - 11:19:04 up 277 days, 54 min,  2 users,  load average: 0.00, 0.00, 0.00
    Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
    Cpu(s):  0.2%us,  0.0%sy,  0.0%ni, 99.8%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
    Mem:   3921488k total,  3176908k used,   744580k free,   327152k buffers
    Swap:  4063228k total,    58672k used,  4004556k free,   434392k cached
    
      PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                                                                                                                         
    17238 root      20   0 6975m 2.0g  11m S  0.3 53.1   0:08.24 java       
    

    当控制台打印memory used >80 hold 1s时,此时进程17238占用的物理内存达到最大值2G。

    jmap

    既然java进程占用的内存较大,在jvm中占用内存最大的区域为堆,存在内存泄漏可能性最大的也是堆,所以使用jvm提供的jmap指令来查看堆的情况。

    # jmap -heap 17238
    Attaching to process ID 17238, please wait...
    Debugger attached successfully.
    Server compiler detected.
    JVM version is 25.241-b07
    
    using thread-local object allocation.
    Parallel GC with 2 thread(s)
    
    Heap Configuration:
       MinHeapFreeRatio         = 0
       MaxHeapFreeRatio         = 100
       MaxHeapSize              = 1073741824 (1024.0MB)
       NewSize                  = 1073217536 (1023.5MB)
       MaxNewSize               = 1073217536 (1023.5MB)
       OldSize                  = 524288 (0.5MB)
       NewRatio                 = 2
       SurvivorRatio            = 8
       MetaspaceSize            = 10485760 (10.0MB)
       CompressedClassSpaceSize = 2097152 (2.0MB)
       MaxMetaspaceSize         = 10485760 (10.0MB)
       G1HeapRegionSize         = 0 (0.0MB)
    
    Heap Usage:
    PS Young Generation
    Eden Space:
       capacity = 805830656 (768.5MB)
       used     = 274035832 (261.34093475341797MB)
       free     = 531794824 (507.15906524658203MB)
       34.00662781436799% used
    From Space:
       capacity = 133693440 (127.5MB)
       used     = 0 (0.0MB)
       free     = 133693440 (127.5MB)
       0.0% used
    To Space:
       capacity = 133693440 (127.5MB)
       used     = 0 (0.0MB)
       free     = 133693440 (127.5MB)
       0.0% used
    PS Old Generation
       capacity = 524288 (0.5MB)
       used     = 0 (0.0MB)
       free     = 524288 (0.5MB)
       0.0% used
    
    820 interned Strings occupying 55872 bytes.
    

    从堆的日志中可以发现,JVM的堆和元空间总共使用内存1.1G左右,说明不是堆内存泄漏。

    jstack

    既然不是堆内存泄露,那么是不是栈的内存泄漏了呢?

    # jstack 17238
    2020-08-21 11:46:21
    Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.241-b07 mixed mode):
    
    "Attach Listener" #8 daemon prio=9 os_prio=0 tid=0x00007f0544001000 nid=0x449d waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007f057c0ba000 nid=0x444f runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f057c0b7000 nid=0x444e waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f057c0b4800 nid=0x444d waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f057c0af800 nid=0x444c runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f057c081800 nid=0x444b in Object.wait() [0x00007f055fefd000]
       java.lang.Thread.State: WAITING (on object monitor)
    	at java.lang.Object.wait(Native Method)
    	- waiting on <0x00000000c0088ee0> (a java.lang.ref.ReferenceQueue$Lock)
    	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
    	- locked <0x00000000c0088ee0> (a java.lang.ref.ReferenceQueue$Lock)
    	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
    	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
    
    "Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f057c07d000 nid=0x444a in Object.wait() [0x00007f055fffe000]
       java.lang.Thread.State: WAITING (on object monitor)
    	at java.lang.Object.wait(Native Method)
    	- waiting on <0x00000000c0086c00> (a java.lang.ref.Reference$Lock)
    	at java.lang.Object.wait(Object.java:502)
    	at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
    	- locked <0x00000000c0086c00> (a java.lang.ref.Reference$Lock)
    	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
    
    "main" #1 prio=5 os_prio=0 tid=0x00007f057c009000 nid=0x4446 waiting on condition [0x00007f0583977000]
       java.lang.Thread.State: TIMED_WAITING (sleeping)
    	at java.lang.Thread.sleep(Native Method)
    	at LeakDemo.main(LeakDemo.java:130)
    
    "VM Thread" os_prio=0 tid=0x00007f057c073800 nid=0x4449 runnable 
    
    "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f057c01e800 nid=0x4447 runnable 
    
    "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f057c020800 nid=0x4448 runnable 
    
    "VM Periodic Task Thread" os_prio=0 tid=0x00007f057c0bd000 nid=0x4450 waiting on condition 
    
    JNI global references: 5
    

    从栈的日志中可以发现,这个进程总共开启了10多个线程,在jvm中每个线程分配的栈的大小(-Xss)默认为1M,说明不是栈内存泄漏。

    到这里基本上可以确定应该是直接内存发生了泄漏。

    NMT

    NMT(NativeMemoryTracking)是JVM提供的用来追踪Native内存的使用情况。通过在启动参数上加入-XX:NativeMemoryTracking=detail就可以启用,然后使用jcmd命令,就可查看内存分配。

    先把程序关闭,加上-XX:NativeMemoryTracking=detail参数重新启动:

    # jcmd 17845 VM.native_memory summary
    17845:
    
    Native Memory Tracking:
    
    Total: reserved=2416216KB, committed=1117248KB
    -                 Java Heap (reserved=1048576KB, committed=1048576KB)
                                (mmap: reserved=1048576KB, committed=1048576KB) 
     
    -                     Class (reserved=1059970KB, committed=8066KB)
                                (classes #414)
                                (malloc=3202KB #280) 
                                (mmap: reserved=1056768KB, committed=4864KB) 
     
    -                    Thread (reserved=12388KB, committed=12388KB)
                                (thread #13)
                                (stack: reserved=12336KB, committed=12336KB)
                                (malloc=38KB #64) 
                                (arena=14KB #22)
     
    -                      Code (reserved=249713KB, committed=2649KB)
                                (malloc=113KB #447) 
                                (mmap: reserved=249600KB, committed=2536KB) 
     
    -                        GC (reserved=40417KB, committed=40417KB)
                                (malloc=3465KB #112) 
                                (mmap: reserved=36952KB, committed=36952KB) 
     
    -                  Compiler (reserved=137KB, committed=137KB)
                                (malloc=6KB #31) 
                                (arena=131KB #5)
     
    -                  Internal (reserved=3290KB, committed=3290KB)
                                (malloc=3258KB #1328) 
                                (mmap: reserved=32KB, committed=32KB) 
     
    -                    Symbol (reserved=1373KB, committed=1373KB)
                                (malloc=917KB #92) 
                                (arena=456KB #1)
     
    -    Native Memory Tracking (reserved=177KB, committed=177KB)
                                (malloc=114KB #1618) 
                                (tracking overhead=63KB)
     
    -               Arena Chunk (reserved=174KB, committed=174KB)
                                (malloc=174KB) 
    
    

    可惜的是,这个名字让人振奋的工具并不能如它描述的一样,看到我们这种泄漏的场景。上面日志中这点小小的空间,是不能和2GB的内存占用相比的。

    perf

    下面介绍一个神器perf,它除了能够进行一些性能分析,它还能帮助我们找到相应的native调用,这么突出的堆外内存使用问题,肯定能找到相应的调用函数。

    安装:yum install -y perf

    当java进程启动时,我们使用命令perf record -g -p 2747开启监控栈函数调用,当程序内存使用的阈值增加到80%,使用Ctrl+C结束perf命令,这时会在当前目录下生成一个文件perf.data。

    然后执行perf report -i perf.data查看报告:

    Samples: 64K of event 'cpu-clock', Event count (approx.): 16226750000                                                                                                                                                                                                         
      Children      Self  Command  Shared Object       Symbol                                                                                                                                                                                                                     
    +   69.64%     0.05%  java     [kernel.kallsyms]   [k] system_call_fastpath
    +   42.45%     0.08%  java     libc-2.17.so        [.] __GI___libc_read
    +   42.22%     0.03%  java     [kernel.kallsyms]   [k] sys_read
    +   42.14%     0.13%  java     [kernel.kallsyms]   [k] vfs_read
    +   41.66%     0.08%  java     [kernel.kallsyms]   [k] proc_reg_read
    +   36.66%     0.20%  java     [kernel.kallsyms]   [k] seq_read
    +   36.22%     0.33%  java     [kernel.kallsyms]   [k] meminfo_proc_show
    +   28.52%    28.51%  java     [kernel.kallsyms]   [k] get_vmalloc_info
    +   13.59%     0.00%  java     [unknown]           [k] 0x702f006f666e696d
    +   13.35%     0.07%  java     libc-2.17.so        [.] __fopen_internal
    +   13.04%     0.07%  java     libc-2.17.so        [.] __GI___libc_open
    +   12.88%     0.04%  java     [kernel.kallsyms]   [k] sys_open
    +   12.78%     0.17%  java     [kernel.kallsyms]   [k] do_sys_open
    +   11.80%     0.04%  java     [kernel.kallsyms]   [k] do_filp_open
    +   11.70%     0.09%  java     [kernel.kallsyms]   [k] path_openat
    +    8.40%     0.48%  java     [kernel.kallsyms]   [k] do_last
    +    7.95%     0.00%  java     [kernel.kallsyms]   [k] page_fault
    +    7.95%     0.01%  java     [kernel.kallsyms]   [k] do_page_fault
    +    7.81%     1.41%  java     [kernel.kallsyms]   [k] __do_page_fault
    +    6.60%     0.32%  java     libjvm.so           [.] JavaCalls::call_helper
    +    6.33%     0.12%  java     [kernel.kallsyms]   [k] seq_printf
    +    6.30%     0.00%  java     libpthread-2.17.so  [.] start_thread
    +    6.21%     0.05%  java     [kernel.kallsyms]   [k] seq_vprintf
    +    6.08%     0.00%  java     perf-2747.map       [.] 0x00007fb799150574
    +    6.02%     0.00%  java     perf-2747.map       [.] 0x00007fb799121c46
    +    5.91%     0.35%  java     [kernel.kallsyms]   [k] handle_mm_fault
    +    5.78%     1.05%  java     [kernel.kallsyms]   [k] vsnprintf
    +    5.74%     0.07%  java     libc-2.17.so        [.] __GI___munmap
    +    5.71%     0.08%  java     libzip.so           [.] Java_java_util_zip_Inflater_inflateBytes
    +    5.68%     0.00%  java     libjli.so           [.] JavaMain
    +    5.68%     0.00%  java     libjvm.so           [.] jni_CallStaticVoidMethod
    +    5.68%     0.00%  java     libjvm.so           [.] jni_invoke_static
    +    5.58%     0.03%  java     [kernel.kallsyms]   [k] sys_munmap
    +    5.39%     0.03%  java     [kernel.kallsyms]   [k] vm_munmap
    +    5.21%     0.25%  java     [kernel.kallsyms]   [k] do_munmap
    +    5.14%     0.07%  java     [kernel.kallsyms]   [k] proc_root_lookup
    +    5.12%     0.01%  java     [kernel.kallsyms]   [k] lookup_real
    +    5.09%     0.03%  java     [kernel.kallsyms]   [k] proc_lookup
    +    4.99%     0.46%  java     [kernel.kallsyms]   [k] proc_lookup_de
    +    4.95%     0.24%  java     libc-2.17.so        [.] __GI___libc_close
    +    4.74%     0.00%  java     perf-2747.map       [.] 0x00007fb799146471
    +    4.67%     1.35%  java     libzip.so           [.] inflate
    +    4.47%     0.15%  java     libjvm.so           [.] JVM_Sleep
    +    4.45%     0.31%  java     [kernel.kallsyms]   [k] __alloc_pages_nodemask
    +    4.19%     0.00%  java     perf-2747.map       [.] 0x00007fb7990007a7
    +    4.15%     0.09%  java     [kernel.kallsyms]   [k] alloc_pages_vma
    +    4.06%     0.06%  java     [kernel.kallsyms]   [k] do_notify_resume
    +    4.06%     0.00%  java     [kernel.kallsyms]   [k] int_signal
    +    3.99%     0.09%  java     [kernel.kallsyms]   [k] task_work_run
    +    3.94%     0.26%  java     libjvm.so           [.] os::sleep
    +    3.89%     0.02%  java     [kernel.kallsyms]   [k] ____fput
    

    一些JNI程序或者JDK内的模块,都会调用相应的本地函数,在Linux上,这些函数库的后缀都是so,windows下函数库的后缀是dll。

    我们依次浏览所有可疑的资源,发现了“libzip.so”,还发现了不少相关的调用。搜索zip(输入 / 进入搜索模式),结果如下:

    Samples: 64K of event 'cpu-clock', Event count (approx.): 16226750000                                                                                                                                                                                                         
      Children      Self  Comm  Shared Ob Symbol                                                                                                                                                                                                                                 
    +    5.71%     0.08%  java  libzip.so  [.] Java_java_util_zip_Inflater_inflateBytes                                                                                                                                                                                          
         0.23%     0.04%  java  libzip.so  [.] Java_java_util_zip_Inflater_init                                                                                                                                                                                                  
    +   42.22%     0.03%  java  [kernel.kallsyms]   [k] sys_read    
    

    我们发现这些本地调用都是由java.util.zip.Inflater#inflateBytes()方法产生的,然后在代码中追踪哪些地方调用了这个方法或者类,一步一步分析。

    内存泄漏的原因

    GZIPInputStream使用Inflater申请堆外内存、我们没有调用close()方法来主动释放。如果忘记关闭,Inflater对象的生命会延续到下一次GC,在此过程中,堆外内存会一直增长,而GC迟迟没有发生。

        public static String decompress(byte[] input) throws Exception {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            copy(new GZIPInputStream(new ByteArrayInputStream(input)), out);
            return new String(out.toByteArray());
        }
    

    只需要将上面的代码改成如下即可:

        public static String decompress(byte[] input) throws Exception {
    
            try (
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    GZIPInputStream gzip = new GZIPInputStream(new ByteArrayInputStream(input))
            ) {
                copy(gzip, out);
                return new String(out.toByteArray());
            }
        }
    

    总结

    对堆外内存划分为3块:

    • 元空间:主要是方法区和常量池的存储之地,可以使用“MaxMetaspaceSize”参数来限制它的大小。
    • 直接内存:主要是通过DirectByteBuffer申请的内存,可以使用“MaxDirectMemorySize”参数来限制它的大小。
    • 其他堆外内存:主要是指使用了Unsafe或者其他JNI手段直接申请的内存。这种情况,就没有任何参数能够阻挡它们,要么靠它自己去释放一些内存,要么等待操作系统对它的审判了。
    展开全文
  • 堆外内存与堆内内存详解一、什么是堆外内存1、堆内内存(on-heap memory)回顾堆外内存和堆内内存是相对的二个概念,其中堆内内存是我们平常工作中接触比较多的,我们在jvm参数中只要使用-Xms,-Xmx等参数就可以设置堆...

    堆外内存与堆内内存详解

    一、什么是堆外内存

    1、堆内内存(on-heap memory)回顾

    堆外内存和堆内内存是相对的二个概念,其中堆内内存是我们平常工作中接触比较多的,我们在jvm参数中只要使用-Xms,-Xmx等参数就可以设置堆的大小和最大值,理解jvm的堆还需要知道下面这个公式:

    堆内内存 = 新生代+老年代+持久代

    如下面的图所示:

    2bcbf2f40fdec1f1d2a6b96f2e3ccb9f.png

    在使用堆内内存(on-heap memory)的时候,完全遵守JVM虚拟机的内存管理机制,采用垃圾回收器(GC)统一进行内存管理,GC会在某些特定的时间点进行一次彻底回收,也就是Full GC,GC会对所有分配的堆内内存进行扫描,在这个过程中会对JAVA应用程序的性能造成一定影响,还可能会产生Stop The World。

    常见的垃圾回收算法主要有:

    • 引用计数器法(Reference Counting)

    • 标记清除法(Mark-Sweep)

    • 复制算法(Coping)

    • 标记压缩法(Mark-Compact)

    • 分代算法(Generational Collecting)

    • 分区算法(Region)

    2、堆外内存(off-heap memory)介绍

    和堆内内存相对应,堆外内存就是把内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机),这样做的结果就是能够在一定程度上减少垃圾回收对应用程序造成的影响。

    作为JAVA开发者我们经常用java.nio.DirectByteBuffer对象进行堆外内存的管理和使用,它会在对象创建的时候就分配堆外内存。

    DirectByteBuffer类是在Java Heap外分配内存,对堆外内存的申请主要是通过成员变量unsafe来操作,下面介绍构造方法

    DirectByteBuffer(int cap) {                          super(-1, 0, cap, cap);        //内存是否按页分配对齐        boolean pa = VM.isDirectMemoryPageAligned();        //获取每页内存大小        int ps = Bits.pageSize();        //分配内存的大小,如果是按页对齐方式,需要再加一页内存的容量        long size = Math.max(1L, (long)cap + (pa ? ps : 0));        //用Bits类保存总分配内存(按页分配)的大小和实际内存的大小        Bits.reserveMemory(size, cap);         long base = 0;        try {           //在堆外内存的基地址,指定内存大小            base = unsafe.allocateMemory(size);        } catch (OutOfMemoryError x) {            Bits.unreserveMemory(size, cap);            throw x;        }        unsafe.setMemory(base, size, (byte) 0);        //计算堆外内存的基地址        if (pa && (base % ps != 0)) {            // Round up to page boundary            address = base + ps - (base & (ps - 1));        } else {            address = base;        }        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));        att = null;

    注:在Cleaner 内部中通过一个列表,维护了一个针对每一个 directBuffer 的一个回收堆外内存的 线程对象(Runnable),回收操作是发生在 Cleaner 的 clean() 方法中。

    private static class Deallocator implements Runnable  {    private static Unsafe unsafe = Unsafe.getUnsafe();    private long address;    private long size;    private int capacity;    private Deallocator(long address, long size, int capacity) {        assert (address != 0);        this.address = address;        this.size = size;        this.capacity = capacity;    }     public void run() {        if (address == 0) {            // Paranoia            return;        }        unsafe.freeMemory(address);        address = 0;        Bits.unreserveMemory(size, capacity);    }

    二、使用堆外内存的优点

    1、减少了垃圾回收

    因为垃圾回收会暂停其他的工作。

    2、加快了复制的速度

    堆内在flush到远程时,会先复制到直接内存(非堆内存),然后在发送;而堆外内存相当于省略掉了这个工作。

    同样任何一个事物使用起来有优点就会有缺点,堆外内存的缺点就是内存难以控制,使用了堆外内存就间接失去了JVM管理内存的可行性,改由自己来管理,当发生内存溢出时排查起来非常困难。

    三、使用DirectByteBuffer的注意事项

    java.nio.DirectByteBuffer对象在创建过程中会先通过Unsafe接口直接通过os::malloc来分配内存,然后将内存的起始地址和大小存到java.nio.DirectByteBuffer对象里,这样就可以直接操作这些内存。这些内存只有在DirectByteBuffer回收掉之后才有机会被回收,因此如果这些对象大部分都移到了old,但是一直没有触发CMS GC或者Full GC,那么悲剧将会发生,因为你的物理内存被他们耗尽了,因此为了避免这种悲剧的发生,通过-XX:MaxDirectMemorySize来指定最大的堆外内存大小,当使用达到了阈值的时候将调用System.gc来做一次full gc,以此来回收掉没有被使用的堆外内存。

    四、DirectByteBuffer使用测试

    我们在写NIO程序经常使用ByteBuffer来读取或者写入数据,那么使用ByteBuffer.allocate(capability)还是使用ByteBuffer.allocteDirect(capability)来分配缓存了?第一种方式是分配JVM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对较慢;第二种方式是分配OS本地内存,不属于GC管辖范围,由于不需要内存拷贝所以速度相对较快。

    代码如下:

    package com.stevex.app.nio; import java.nio.ByteBuffer;import java.util.concurrent.TimeUnit; public class DirectByteBufferTest {    public static void main(String[] args) throws InterruptedException{            //分配128MB直接内存        ByteBuffer bb = ByteBuffer.allocateDirect(1024*1024*128);                 TimeUnit.SECONDS.sleep(10);        System.out.println("ok");    }
    测试用例1:设置JVM参数-Xmx100m,运行异常,因为如果没设置-XX:MaxDirectMemorySize,则默认与-Xmx参数值相同,分配128M直接内存超出限制范围。Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory    at java.nio.Bits.reserveMemory(Bits.java:658)    at java.nio.DirectByteBuffer.(DirectByteBuffer.java:123)    at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:306)    at com.stevex.app.nio.DirectByteBufferTest.main(DirectByteBufferTest.java:8)    测试用例2:设置JVM参数-Xmx256m,运行正常,因为128M小于256M,属于范围内分配。测试用例3:设置JVM参数-Xmx256m     -XX:MaxDirectMemorySize=100M,运行异常,分配的直接内存128M超过限定的100M。    Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory    at java.nio.Bits.reserveMemory(Bits.java:658)    at java.nio.DirectByteBuffer.(DirectByteBuffer.java:123)    at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:306)    at com.stevex.app.nio.DirectByteBufferTest.main(DirectByteBufferTest.java:8)

    测试用例3:设置JVM参数-Xmx256m -XX:MaxDirectMemorySize=100M,运行异常,分配的直接内存128M超过限定的100M。

    Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory    at java.nio.Bits.reserveMemory(Bits.java:658)    at java.nio.DirectByteBuffer.(DirectByteBuffer.java:123)    at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:306)    at com.stevex.app.nio.DirectByteBufferTest.main(DirectByteBufferTest.java:8)

    测试用例4:设置JVM参数-Xmx768m,运行程序观察内存使用变化,会发现clean()后内存马上下降,说明使用clean()方法能有效及时回收直接缓存。

    代码如下:

    package com.stevex.app.nio; import java.nio.ByteBuffer;import java.util.concurrent.TimeUnit;import sun.nio.ch.DirectBuffer; public class DirectByteBufferTest {    public static void main(String[] args) throws InterruptedException{        //分配512MB直接缓存        ByteBuffer bb = ByteBuffer.allocateDirect(1024*1024*512);                 TimeUnit.SECONDS.sleep(10);                 //清除直接缓存        ((DirectBuffer)bb).cleaner().clean();                 TimeUnit.SECONDS.sleep(10);                 System.out.println("ok");    }

    System.gc的作用有哪些

    说起堆外内存免不了要提及System.gc方法,下面就是使用了System.gc的作用是什么?

    • 做一次full gc

    • 执行后会暂停整个进程。

    • System.gc我们可以禁掉,使用-XX:+DisableExplicitGC,

    其实一般在cms gc下我们通过-XX:+ExplicitGCInvokesConcurrent也可以做稍微高效一点的gc,也就是并行gc。

    • 最常见的场景是RMI/NIO下的堆外内存分配等

    注:

    如果我们使用了堆外内存,并且用了DisableExplicitGC设置为true,那么就是禁止使用System.gc,这样堆外内存将无从触发极有可能造成内存溢出错误,在这种情况下可以考虑使用ExplicitGCInvokesConcurrent参数。

    说起Full gc我们最先想到的就是stop thd world,这里要先提到VMThread,在jvm里有这么一个线程不断轮询它的队列,这个队列里主要是存一些VM_operation的动作,比如最常见的就是内存分配失败要求做GC操作的请求等,在对gc这些操作执行的时候会先将其他业务线程都进入到安全点,也就是这些线程从此不再执行任何字节码指令,只有当出了安全点的时候才让他们继续执行原来的指令,因此这其实就是我们说的stop the world(STW),整个进程相当于静止了。

    六、开源堆外缓存框架

    关于堆外缓存的开源实现。查询了一些资料后了解到的主要有:

    • Ehcache 3.0:3.0基于其商业公司一个非开源的堆外组件的实现。

    • Chronical Map:OpenHFT包括很多类库,使用这些类库很少产生垃圾,并且应用程序使用这些类库后也很少发生Minor GC。类库主要包括:Chronicle Map,Chronicle Queue等等。

    • OHC:来源于Cassandra 3.0, Apache v2。

    • Ignite: 一个规模宏大的内存计算框架,属于Apache项目。

    展开全文
  • 堆外内存简介DirectByteBuffer 这个类是 JDK 提供使用堆外内存的一种途径,当然常见的业务开发一般不会接触到,即使涉及到也可能是框架(如 Netty、RPC 等)使用的,对框架使用者来说也是透明的。堆外内存优势堆外内存...
    e212769b810fb2b25a1e83e92762c8d4.png

    堆外内存简介

    DirectByteBuffer 这个类是 JDK 提供使用堆外内存的一种途径,当然常见的业务开发一般不会接触到,即使涉及到也可能是框架(如 Netty、RPC 等)使用的,对框架使用者来说也是透明的。

    堆外内存优势

    堆外内存优势在 IO 操作上,对于网络 IO,使用 Socket 发送数据时,能够节省堆内存到堆外内存的数据拷贝,所以性能更高。看过 Netty 源码的同学应该了解,Netty 使用堆外内存池来实现零拷贝技术。对于磁盘 IO 时,也可以使用内存映射,来提升性能。另外,更重要的几乎不用考虑堆内存烦人的 GC 问题。

    堆外内存创建

    我们直接来看代码,首先向 Bits 类申请额度,Bits 类内部维护着当前已经使用的堆外内存值,会 check 当前申请的大小与已经使用的内存大小是否超过总的堆外内存大小(默认大小与堆内存差不多,其实是有细微区别的,拿 CMS GC 来举例,它的大小是新生代的最大值 - 一个 survivor 的大小 + 老生代的最大值),可以使用 -XX:MaxDirectMemorySize 参数指定堆外内存最大大小。

    e05825a8da4dfec43853a5ae990e9f2c.png

    如果 check 不通过,会主动执行 System.gc(),然后 sleep 100 毫秒,再进行 check,如果内存还是不足,就抛出 OOM Error。

    如果 check 通过,就会调用 unsafe.allocateMemory 真正分配内存,返回内存地址,然后再将内存清 0。题外话,这个 unsafe 命名看着是不是很吓人,这个 unsafe 不是说不安全,而是 JDK 内部使用的类,不推荐外部使用,所以叫 unsafe,Netty 源码内部也有类似命名。

    由于申请内存前可能会调用 System.gc(),所以谨慎设置 -XX:+DisableExplicitGC 这个选项,这个参数作用是禁止代码中显示触发的 Full GC。

    堆外内存回收

    cleaner = Cleaner.create(this, new Deallocator (base, size, cap));

    看到这段代码从成员的命名上就应该知道,是用来回收堆外内存的。确实,但是它是如何工作的呢?接下来我们看看 Cleaner 类。

    ccb814292e2de8aefca0f24275be9737.png

    Cleaner 类,内部维护了一个 Cleaner 对象的链表,通过 create(Object, Runnable) 方法创建 cleaner 对象,调用自身的 add 方法,将其加入到链表中。更重要的是提供了 clean 方法,clean 方法首先将对象自身从链表中删除,保证只调用一次,然后执行 this.thunk 的 run 方法,thunk 就是由创建时传入的 Runnable 参数,也就是说 clean 只负责触发 Runnable 的 run 方法,至于 Runnable 做什么任务它不关心。

    那 DirectByteBuffer 传进来的 Runnable 是什么呢?

    86cbd2019892cda0deb85ff3c609d323.png

    Deallocator 类的对象就是 DirectByteBuffer 中的 cleaner 传进来的 Runnable 参数类,我们直接看 run 方法 unsafe.freeMemory 释放内存,然后更新 Bits 里已使用的内存数据。

    接下来我们关注各个环节是如何串起来的?这里主要讲两种回收方式:一种是自动回收,一种是手动回收。

    如何自动回收?

    Java 是不用用户去管理内存的,所以 Java 对堆外内存 默认是自动回收的。它是 由 GC 模块负责的,在 GC 时会扫描 DirectByteBuffer 对象是否有有效引用指向该对象,如没有,在回收 DirectByteBuffer 对象的同时且会回收其占用的堆外内存。但是 JVM 如何释放其占用的堆外内存呢?如何跟 Cleaner 关联起来呢?

    这得从 Cleaner 继承了 PhantomReference(虚引用) 说起。说到 Reference,还有 SoftReference、WeakReference、FinalReference 他们作用各不相同,这里就不展开说了。

    简单介绍 PhantomReference,首先虚引用是不会影响 JVM 去回收其指向的对象,当 GC 某个对象时,如果有此对象上还有虚引用对其引用,会将 PhantomReference 对象插入 ReferenceQueue 队列。

    PhantomReference插入到哪个队列呢?看 PhantomReference 类代码,其继承自 Reference,Reference 对象有个 ReferenceQueue 成员,这个也就是 PhantomReference 对象插入的 ReferenceQueue 队列,此成员如果不由外部传入就是 ReferenceQueue.NULL。如果需要通过 queue 拿到 PhantomReference 对象,这个 ReferenceQueue 对象还是必须由外部传入。

    88c791f1485dcf34d273f60912778228.png

    Reference 类内部 static 静态块会启动 ReferenceHandler 线程,线程优先级很高,这个线程是用来处理 JVM 在 GC 过程中交接过来的 reference。想必经常用 jstack 命令,看线程堆栈的同学应该见到过这个线程。

    我们来看看 ReferenceHandler 是如何处理的?直接看 run 方法,首先是个死循环,一直在那不停的干活,synchronized 块内的这段主要是交接 JVM 扔过来的 reference(就是 pending),再往下看,很明显,调用了 cleaner 的 clean 方法。调完之后直接 continue 结束此次循环,这个 reference 并没有进入 queue,也就是说 Cleaner 虚引用是不放入 ReferenceQueue。

    034c7810946cbf273fc16ada37af4c6d.png

    这块有点想不通,既然不放入 ReferenceQueue,为什么 Cleaner 类还是初始化了这个 ReferenceQueue。

    如何手动回收?

    手动回收,就是由开发手动调用 DirectByteBuffer 的 cleaner 的 clean 方法来释放空间。由于 cleaner 是 private 反问权限,所以自然想到使用反射来实现。

    d1d406ffde422afebb750309da55db15.png

    还有另一种方法,DirectByteBuffer 实现了 DirectBuffer 接口,这个接口有 cleaner 方法可以获取 cleaner 对象。

    4f950aae386a665f21ecd6ee86aca546.png

    Netty 中的堆外内存池就是使用反射来实现手动回收方式进行回收的。


    很多时候,我们在学习一门技术时候往往限于眼前而很难突破,也就是通俗意义上的思维固化,这个时候你就需要用另一种思维来打破现状。学习的目的在于扩宽自己的视野、发散自己的思维,以更完备的视角去迎接人生的抉择。

    关注我,后续更多干货奉上!

    展开全文
  • 堆外内存与堆内内存详解

    万次阅读 多人点赞 2018-05-07 17:14:42
    堆外内存一直是Java业务开发人员难以企及的隐藏领域,究竟他是干什么的,以及如何更好的使用呢?那就请跟着我进入这个世界吧。一、什么是堆外内存1、堆内内存(on-heap memory)回顾堆外内存和堆内内存是相对的二个...
  • 1、堆外内存定义内存对象分配在JVM中堆以外的内存,也可以称为直接内存,这些内存直接受操作系统管理(而不是JVM),这样做的好处是能够在一定程度上减少垃圾回收对应用程序造成的影响。一般我们使用Unsafe和NIO包下...
  • 专注于Java领域优质技术号,欢迎关注作者:占小狼堆外内存JVM启动时分配的内存,称为堆内存,与之相对的,在代码中还可以使用堆外内存,比如Netty,广泛使用了堆外内存,但是这部分的内存并不归JVM管理,GC算法并...
  • 一、堆外内存组成 通常JVM的参数我们会配置 -Xms 堆初始内存 -Xmx 堆最大内存 -XX:+UseG1GC/CMS 垃圾回收器 -XX:+DisableExplicitGC 禁止显示GC -XX:MaxDirectMemorySize 设置最大堆外内存,默认是-xmx-survivor,...
  • 堆外内存与堆内内存

    2019-07-11 14:02:17
    堆外内存一直是Java业务开发人员难以企及的隐藏领域,究竟他是干什么的,以及如何更好的使用呢?那就请跟着我进入这个世界吧。 一、什么是堆外内存 1、堆内内存(on-heap memory)回顾 堆外内存和堆内内存是相对的二...
  • 最近排查一个线上java...由于Xmx只配置了8G但RES常驻内存达到了13G,多出了5G堆外内存,经验上判断这里超出太多不太正常。前情提要–JVM内存模型开始逐步对堆外内存进行排查,首先了解一下JVM内存模型。根据JVM规范...
  • 堆外内存和堆内内存

    2018-11-28 21:05:58
    堆外内存和堆内内存是相对的二个概念,其中堆内内存是我们平常工作中接触比较多的,我们在jvm参数中只要使用-Xms,-Xmx等参数就可以设置堆的大小和最大值,理解jvm的堆还需要知道下面这个公式: 堆内内存 = 新生代+...
  • 堆内内存和堆外内存

    2019-06-21 17:34:31
    堆内内存: 堆内内存 = 新生代+老年代+持久代 ...使用堆外内存,就是为了能直接分配和释放内存,提高效率。JDK5.0之后,代码中能直接操作本地内存的方式有2种: 1.使用未公开的Unsafe。 2.NIO包下ByteBuffer。j...
  • 概述广义的堆外内存说到堆外内存,那大家肯定想到堆内内存,这也是我们大家接触最多的,我们在jvm参数里通常设置-Xmx来指定我们的堆的最大值,不过这还不是我们理解的Java堆,-Xmx的值是新生代和老生代的和的最大值...
  • Java堆外内存的使用

    2021-01-21 17:09:50
     堆外内存其实并无特别之处。线程栈,应用程序代码,NIO缓存用的都是堆外内存。事实上在C或者C++中,你只能使用未托管内存,因为它们默认是没有托管堆(managed heap)的。在Java中使用托管内存或者“堆”内存是这...
  • Java堆外内存

    2021-02-12 12:55:26
    1.为啥需要堆外内存? 1.在Java中NIO数据传输的时候使用了堆外内存,为啥要使用呢?堆内内存咋了嘛?一个很大的原因,可能是堆内内存不够会产生GC,导致一定程度的传输速率下降,因此出现其他性能问题。 2.但是有...
  • Spark 堆外内存

    千次阅读 2017-12-13 16:55:44
    1.堆外内存有哪些前面提到spark中的堆内存溢出,除了堆内存,还有堆外内存。该部分内存主要用于程序的共享库、Perm Space、 线程Stack和一些Memory mapping等, 或者类C方式allocate object.堆外内存在Spark中可以从...
  • 但是没有一个人能够说明白什么是堆外内存! 今天我们就一起来简单的说一说 Java 中的堆外内存。这一块可能 90% 的 Java 程序员都不清楚,希望你看过本文后,成为那 10% 中的大神级别的潜力股。 堆外内存是相对于堆内...
  • JAVA堆内内存、堆外内存

    千次阅读 2018-07-15 21:08:07
    定义堆内存完全由JVM负责分配和释放,如果代码有程序缺陷,可能是触发OOM堆外内存为了能直接分配和释放内存,提高效率。使用方式:使用未公开的Unsafe和NIO下的ByteBuffer堆外内存的回收机制Direct Memory是受GC控制...
  • Netty 堆外内存的管理

    2020-11-21 11:12:05
    我们经常看到各类堆外内存泄漏的排查案例,堆外内存使用不当会使得应用出错、崩溃的概率变大,所以在使用堆外内存时一定要慎重,文章将带你一起认识堆外内存,并探讨如何更好地使用它。 文章目录为什么需要堆外内存...
  • 今天我要问你的问题是,如何监控和诊断 JVM 堆内和堆外内存使用?典型回答了解 JVM 内存的方法有很多,具体能力范围也有区别,简单总结如下:可以使用综合性的图形化工具,如 JConsole、VisualVM(注意,从 Oracle ....
  • 堆外内存优势堆外内存的申请ByteBuffer.allocateDirect(1024)堆外内存释放 JVM堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机)。 使用未公开的Unsafe和NIO包下ByteBuffer来创建堆外内存。 优势 减少...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,268
精华内容 1,707
关键字:

堆外内存