java虚拟机15

直接内存

直接内存(堆外内存)

  • 直接内存和堆外内存可以是认为是等同的,直接内存是直接向操作系统申请的,而堆是JVM提前申请的一块内存

  • 直接内存有哪些?

    • java的unsafe类

      package ex15;
      
      import sun.misc.Unsafe;
      
      import java.lang.reflect.Field;
      /**
       * @author King老师
       * 参数无效:-XX:MaxDirectMemorySize=10m
       */
      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);
              long addr = unsafe.allocateMemory(100*_1MB);
          }
      }
      
      

      使用unsafe类后,-XX:MaxDirectMemorySize这个虚拟机参数就无效了,就是这个参数设置的直接内存限制对unsafe无效,同时jdk不允许直接使用unsafe类,只能通过反射的方式来使用

    • netty中ByteBuffer,底层会去和操作系统发生调用

      package ex15;
      
      import java.nio.ByteBuffer;
      
      /**
       * @author King老师
       * VM Args:-XX:MaxDirectMemorySize=100m
       * 限制最大直接内存大小100m
       * -XX:MaxDirectMemorySize=128m
       * -Xmx128m
       * -Xmx135m -Xmn100m -XX:SurvivorRatio=8
       * -Xmx138m -Xmn100m -XX:SurvivorRatio=8
       */
      public class ByteBufferDemo {
          static ByteBuffer bb;
          public static void main(String[] args) throws Exception {
              //直接分配128M的直接内存
              bb = ByteBuffer.allocateDirect(128*1024*1024);
          }
      }
      
      

      ByteBuffer是收到-XX:MaxDirectMemorySize的限制的,超过后会抛OOM:direct memory

    • JNI或者JNA,JNI–java本地接口,java自带的native方法,底层是一个dll,JNA–java本地访问,JNI的封装,使用起来可以不用编写native方法,我们在java接口中间描述一个native函数的结构,JNA会自动封装一个到本地函数的映射

为什么要使用直接内存

  • 不受JVM控制

  • 为什么?

    • 1.减少垃圾回收的工作,STW

    • 2.加快复制的速度,网络通讯时,堆上的数据需要被复制到直接内存上,这件事是操作系统底层做的,零拷贝

    • 3.进程间的共享

    • 4.堆受制于虚拟化的程度,如果虚拟机的内存是1tb或者以上,因为做虚拟化开销很大,会有性能损耗,还不如使用直接内存

直接内存的另一面

  • 1.难以控制,出现内存泄漏,很难排查
  • 2.相对于堆,不适合存储复杂对象,适用于简单对象

直接内存

内存泄漏案例

  • package ex15;
    
    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;
    
    /**
     * @author  King老师
     *
     *  -XX:+PrintGC -Xmx1G -Xmn1G
     *  -XX:+AlwaysPreTouch
     *  -XX:MaxMetaspaceSize=10M
     *  -XX:MaxDirectMemorySize=10M
     */
    public class LeakProblem {
        /**
         * 构造随机的字符串
         */
        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 {
            //模拟一个http请求--提高内存阈值
            HttpServer server = HttpServer.create(new InetSocketAddress(8888), 0);
            HttpContext context = server.createContext("/");
            context.setHandler(exchange -> {
                try {
                    RADIO = 85;
                    String response = "OK!";
                    exchange.sendResponseHeaders(200, response.getBytes().length);
                    OutputStream os = exchange.getResponseBody();
                    os.write(response.getBytes());
                    os.close();
                } catch (Exception ex) {
                }
            });
            server.start();
    
    
            //构造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);
                }
            }
        }
    }
    
    
  • GZIPOutputStream,java自带的类,可以对数据进行压缩和解压缩

  • java -cp ref-jvm3.jar -XX:+PrintGC -Xmx1G -Xmn1G -XX:+AlwaysPreTouch -XX:MaxMetaspaceSize=10M -XX:MaxDirectMemorySize=10M e15.LeakProblem

    • -XX:AlwaysPreTouch,jvm启动时,为所有的内存都进行了系统分配,但是这个内存分配不一定及时提交了,启用这个参数后会立即提交

常规排查方式

  • top

    • 然后top -p pid,只看指定进程的信息

    • PID  USER  PR NI    VIRT    RES   SHR S %CPU %MEM   TIME+ COMMAND
      3555 root  20  0 5452628 1.541g 12140 S  0.3 41.6 0:19.05 java 
      
      • RES,常驻内存,resident memory usage,进程申请内存申请100M,但是实际只使用了10M,此时RES就是10M,如果没有标明单位,默认是比特
      • VIRT,虚拟内存,virtual memory usage,进程申请内存申请100M,但是实际只使用了10M,此时VIRT就是100M
    • 发现启动的java程序的cpu使用率不高,但是mem使用率很高,并且不断增加

    • RES是1.54g,但是只设置1g,结果不正常?

      • 案例代码中,如果内存使用率超过60%,线程就会睡眠

      • 使用jmap -heap,发现堆确实只使用了1个g,使用jstack xxx,发现也只有几个线程,那样占的内存也只有几M而已,那合起来还是不对

      • jmap -histo xxx | head -20,发现最大的实例对象也只有300M,还是不对

      • 使用mat来分析内存,生成一个dump.bin文件,在mat中打开

      • 首先mat猜想是类加载器导致的,但是只有30M左右的大小,是不对的

      • 这个时候就要怀疑是否是直接内存泄漏,java自带一个工具–NMT,native memory tracking,需要加一个虚拟机配置参数,-XX:NativeMemoryTracking=detail,这个参数不是可配置的(product是生产,这种是不可动态配置的,manageable是可动态配置),并且默认是关闭的,所以需要重启服务(使用命令查看是否可配置–java -XX:+PrintFlagsFinal -version)

使用工具排查

内存泄漏问题解决

  • 修改启动命令
  • ps -ef | grep java
  • kill -9 3555
  • ls,发现已杀死的命令
  • java -cp ref-jvm3.jar -XX:+PrintGC -Xmx1G -Xmn1G -XX:+AlwaysPreTouch -XX:MaxMetaspaceSize=10M -XX:MaxDirectMemorySize=10M -XX:NativeMemoryTracking=detail e15.LeakProblem
  • 重启完成后,发现RES在不断变大
  • 输入命令,jcmd xxx VM.native_memory summary
    • 发现native方法也只用了几百KB,反应不出来问题
    • 这个时候需要用到更专业的运维来处理,专业的查看直接内存泄漏的工具perf(坑,很多时候操作系统内存功能没开启,没支持,也分析不出来,需要开启)
  • 这个时候考虑直接内存在java中只有三种方式,而三种中更可能的是JNI方法,而gzip中使用了JNI方法
    • GZIPInputStram类,Inflater类,init方法—去申请内存
    • 对于这种输入输出流,使用完后要记得关闭,如果没关闭,它的生命周期可以活到gc,因为Inflater是java类,但是是直接内存,虚拟机不会触发垃圾回收,所以直接内存会一直增长

内存泄漏问题解决

  • 在解压方法里面,进行close

  • //解压
    public static String decompress(byte[] input) throws Exception {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        GZIPInputStream gzip = new GZIPInputStream(new ByteArrayInputStream(input));
        try {
            copy(gzip, out);
            return new String(out.toByteArray());
        }finally {
            try{ gzip.close();
               }catch (Exception ex){
            }
            try{
                out.close();
            }catch (Exception ex){
            }
        }
    }
    

JVM源码分析

JVM源码如何查看!

  • https://cloud.tencent.com/developer/article/1585224

场景分析:堆外内存默认值是多大?

  • 堆外内存默认值是堆空间的可使用值,要把survivor区预留的那一部分扣减掉

源码追踪

  • package ex15;
    
    import java.nio.ByteBuffer;
    
    /**
     * @author King老师
     * VM Args:-XX:MaxDirectMemorySize=100m
     * 限制最大直接内存大小100m
     * -XX:MaxDirectMemorySize=128m
     * -Xmx128m
     * -Xmx135m -Xmn100m -XX:SurvivorRatio=8
     * -Xmx138m -Xmn100m -XX:SurvivorRatio=8
     */
    public class ByteBufferDemo {
        static ByteBuffer bb;
        public static void main(String[] args) throws Exception {
            //直接分配128M的直接内存
            bb = ByteBuffer.allocateDirect(128*1024*1024);
        }
    }
    
    
ByteBuffer.allocateDirect
  • public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }
    
new DirectByteBuffer(capacity)
  • DirectByteBuffer(int cap) {                   // package-private
    
        super(-1, 0, cap, cap);
        boolean pa = VM.isDirectMemoryPageAligned();
        int ps = Bits.pageSize();
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
        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;
    
    
    
    }
    
Bits.reserveMemory(size, cap)
  • static void reserveMemory(long size, int cap) {
    
        if (!memoryLimitSet && VM.isBooted()) {
            maxMemory = VM.maxDirectMemory();
            memoryLimitSet = true;
        }
    
        // optimist!
        if (tryReserveMemory(size, cap)) {
            return;
        }
    
        final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();
    
        // retry while helping enqueue pending Reference objects
        // which includes executing pending Cleaner(s) which includes
        // Cleaner(s) that free direct buffer memory
        while (jlra.tryHandlePendingReference()) {
            if (tryReserveMemory(size, cap)) {
                return;
            }
        }
    
        // trigger VM's Reference processing
        System.gc();
    
        // a retry loop with exponential back-off delays
        // (this gives VM some time to do it's job)
        boolean interrupted = false;
        try {
            long sleepTime = 1;
            int sleeps = 0;
            while (true) {
                if (tryReserveMemory(size, cap)) {
                    return;
                }
                if (sleeps >= MAX_SLEEPS) {
                    break;
                }
                if (!jlra.tryHandlePendingReference()) {
                    try {
                        Thread.sleep(sleepTime);
                        sleepTime <<= 1;
                        sleeps++;
                    } catch (InterruptedException e) {
                        interrupted = true;
                    }
                }
            }
    
            // no luck
            throw new OutOfMemoryError("Direct buffer memory");
    
        } finally {
            if (interrupted) {
                // don't swallow interrupts
                Thread.currentThread().interrupt();
            }
        }
    }
    
  • maxMemory–判断oom的重要依据

maxMemory = VM.maxDirectMemory()
  • public static long maxDirectMemory() {
        return directMemory;
    }
    
  • public static void saveAndRemoveProperties(Properties var0) {
        if (booted) {
            throw new IllegalStateException("System initialization has completed");
        } else {
            savedProps.putAll(var0);
            String var1 = (String)var0.remove("sun.nio.MaxDirectMemorySize");
            if (var1 != null) {
                if (var1.equals("-1")) {
                    directMemory = Runtime.getRuntime().maxMemory();
                } else {
                    long var2 = Long.parseLong(var1);
                    if (var2 > -1L) {
                        directMemory = var2;
                    }
                }
            }
    
            var1 = (String)var0.remove("sun.nio.PageAlignDirectMemory");
            if ("true".equals(var1)) {
                pageAlignDirectMemory = true;
            }
    
            var1 = var0.getProperty("sun.lang.ClassLoader.allowArraySyntax");
            allowArraySyntax = var1 == null ? defaultAllowArraySyntax : Boolean.parseBoolean(var1);
            var0.remove("java.lang.Integer.IntegerCache.high");
            var0.remove("sun.zip.disableMemoryMapping");
            var0.remove("sun.java.launcher.diag");
            var0.remove("sun.cds.enableSharedLookupCache");
        }
    }
    
    • System这个类加载时,这个静态方法会被显示调用

    • directMemory = Runtime.getRuntime().maxMemory();

    • maxMemory是一个native方法

    • java中的类Runtime,在c中是Java_java_lang_Runtime_maxMemory,对应的文件是Runtime.c

Runtime.c

  • Java_java_lang_Runtime_maxMemory(JNIEnv *env, jobject this)
    {
        return JVM_MaxMemory();
    }
    
    
  • JVM_ENTRY_NO_ENV(jlong, JVM_MaxMemory(void))
      JVMWrapper("JVM_MaxMemory");
      size_t n = Universe::heap()->max_capacity();
      return convert_size_t_to_jlong(n);
    JVM_END
    
  • size_t GenCollectedHeap::max_capacity() const {
      size_t res = 0;
      for (int i = 0; i < _n_gens; i++) {
        res += _gens[i]->max_capacity();
      }
      return res;
    }
    
    • res是实际使用内存
    • gens包括新生代和老年代
  • size_t DefNewGeneration::max_capacity() const {
      const size_t alignment = GenCollectedHeap::heap()->collector_policy()->space_alignment();
      const size_t reserved_bytes = reserved().byte_size();
      return reserved_bytes - compute_survivor_size(reserved_bytes, alignment);
    }
    
    • 新生代大小=总大小-survivor区的大小

总结

  • JVM门槛很高,体系过多,最好是根据场景切入来阅读源码
    • 比如动态年龄判断
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值