元空间和直接内存

参考https://blog.csdn.net/Ethan_199402/article/details/110431404
https://www.cnblogs.com/zhai1997/p/12912915.html
https://www.zhihu.com/question/456026457/answer/1850516358
https://www.jianshu.com/p/474d98fc4776

元空间和直接内存

元空间和直接内存
随着JDK8的到来,JVM不再有PermGen。但类的元数据信息(metadata)还在,只不过不再是存储在连续的堆空间上,而是移动到叫做“Metaspace”的本地内存(Native memory)中。从上图可以看到,元空间和直接内存都是堆外内存。

Metaspace

  • 永久代
    JDK1.8以前的HotSpot JVM的方法区用永久代(permanent generation)实现。方法区用于存放已被虚拟机加载的类信息、常量、静态变量,即编译器编译后的代码。永久代是一片连续的堆空间,通过-XX:MaxPermSize来设定永久代最大可分配空间,当JVM加载的类信息容量超过了这个值,会报OOM:PermGen错误。
    JDK1.7开始了永久代的部分移除:符号引用(Symbols)移至native heap,字面量(interned strings)和静态变量(class statics)移至java heap。
  • 为什么要用Metaspace替代永久代实现方法区
    随着动态类加载的情况越来越多,这块内存变得不太可控,如果设置小了,系统运行过程中就容易出现内存溢出,设置大了又浪费内存。
    默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:
    XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
    -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
  • 元空间里到底存了什么
    java8中移除了永久代,新增了元空间的概念。原来的方法区是逻辑划分中的一个区域,对应hotspot jdk6中的永久代,可以说永久代是方法区在hotspot的一个具体实现,但是从jdk7以后方法区就“四分五裂了”,不再是在单一的一个去区域内进行存储。
    java8中继承了一些jdk7中的改变:符号引用存储在native heap中,字符串常量和静态类型变量存储在普通的堆区中,这个影响了String的intern()方法的行为,这里不做intern的详述。
    而在java8中移除了永久代,新增了元空间,其实在这两者之间存储的内容几乎没怎么变化,而是在内存限制、垃圾回收等机制上改变较大。元空间的出现就是为了解决突出的类和类加载器元数据过多导致的OOM问题,而从jdk7中开始永久代经过对方法区的分裂后已经几乎只存储类和类加载器的元数据信息了,到了jdk8,元空间中也是存储这些信息,而符号引用、字符串常量等存储位置与jdk7一致,还是“分裂”的方法区。
    符号引用没有存在元空间中,而是存在native heap中,这是两个方式和位置,不过都可以算作是本地内存,在虚拟机之外进行划分,没有设置限制参数时只受物理内存大小限制,即只有占满了操作系统可用内存后才OOM。
  • Metaspace内存管理:
    在metaspace中,类和其元数据的生命周期与其对应的类加载器相同,只要类的类加载器是存活的,在Metaspace中的类元数据也是存活的,不能被回收。
    每个加载器有单独的存储空间,分配给一个类的空间,是归属于这个类的类加载器的,只有当这个类加载器卸载的时候,这个空间才会被释放。所以,只有当这个类加载器加载的所有类都没有存活的对象,并且没有到达这些类和类加载器的引用时,相应的 Metaspace 空间才会被 GC(full gc) 释放。

直接内存

直接内存主要被 Java NIO 使用,某种程度上也就是指DirectByteBuffer对象占用的堆外内存。DirectByteBuffer对象创建时会通过Unsafe类接口直接调用操作系统的malloc分配内存,然后将内存的起始地址和大小保存下来,据此就可以直接操作内存空间。
元空间和直接内存

可以看出,直接内存的大小并不受到java堆大小的限制,甚至不受到JVM进程内存大小的限制。它只受限于本机总内存(RAM及SWAP区或者分页文件)大小以及处理器寻址空间的限制(最常见的就是32位/64位CPU的最大寻址空间限制不同)。

  • DirectByteBuffer使用直接内存的原因有两点:
    (1)减少了垃圾回收
    堆外内存是直接受操作系统管理(不是JVM)。这样做能保持一个较小的堆内内存,以减少垃圾收集对应用的影响。
    (2)提升IO速度
    堆内内存由JVM管理,属于“用户态”;而堆外内存由OS管理,属于“内核态”。如果从堆内向磁盘写数据时,数据会被先复制到堆外内存,即内核缓冲区,然后再由OS写入磁盘,使用堆外内存避免了这个操作。
  • 直接内存的回收
    缺点就是没有JVM协助管理内存,需要自行管理直接内存,防止内存溢出,一般会设置直接内存最大值-XX:MaxDirectMemerySize,当达到阈值的时候,调用system.gc来进行一次full gc,把那些没有被使用的直接内存回收掉。
上一篇:Java 8: 元空间(Metaspace)


下一篇:垃圾回收