美文网首页
JVM学习(4)非堆的配置参数

JVM学习(4)非堆的配置参数

作者: 陈阳001 | 来源:发表于2018-07-14 20:01 被阅读0次

    除了堆内存以外,虚拟机还有一些内存用于方法区,线程栈和直接内存的使用。它们与堆内存是独立的。虽然和堆内存相比,这些内存空间和应用程序可能关系不是那么密切,但从系统层面来看,有效,合理地配置这些内存参数,对系统性能和稳定性有着重要的作用。

    一.元数据区配置(jdk7 的方法区)

    用于存放类的元数据,Metaspace使用的是本地内存,而不是堆内存。

    • 在jdk8中已经将永久带移除了。也就是说-XX:PermSize这些参数在jdk8中将是无效的。 移除了,肯定有人来代替他。就是新出现的元空间(Metaspace)来代替原来的永久带。

    • -XX:MetaspaceSize,class metadata的初始空间配额,以bytes为单位,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当的降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize(如果设置了的话),适当的提高该值。

    • -XX:MaxMetaspaceSize,可以为class metadata分配的最大空间。默认是没有限制的。

    • -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为class metadata分配空间导致的垃圾收集

    • -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为class metadata释放空间导致的垃圾收集

    • 默认情况下,class metadata的分配仅受限于可用的native memory总量。可以使用MaxMetaspaceSize来限制可为class metadata分配的最大内存。当class metadata的使用的内存达到MetaspaceSize(32位clientVM默认12Mbytes,32位ServerVM默认是16Mbytes)时就会对死亡的类加载器和类进行垃圾收集。设置MetaspaceSize为一个较高的值可以推迟垃圾收集的发生。

    • 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。理论上取决于32位/64位系统可虚拟的内存大小。可见也不是无限制的,需要配置参数。

    • 对于僵死的类及类加载器的垃圾回收将在元数据使用达到“MaxMetaspaceSize”参数的设定值时进行。

    二.永久区(方法区)与metadata区的对比:

    PermGen
    方法区:
    1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
    2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
    JAVA 8之前中被称作“Permanent Generation”的特殊区域。这是以前存放例如class的metadata。而,Permgen还存储String之类的额外数据。实际上为JAVA开发者添加了许多麻烦,因为很难预测到底需要多少的空间。这些错误的预测结果表现形式为java.lang.OutOfMemoryError: Permgen space。除非是类似OutOfMemoryError的原因是真的是因为内存泄漏,解决这种问题的简单方法是增加permgen尺寸。下图中设置permgen尺寸的最大值为256M:

    java -XX:MaxPermSize=256m
    

    Metaspace
    JDK8之前,由永久代实现,主要存放类的信息、常量池、方法数据、方法代码等;JDK8之后,取消了永久代,提出了元空间,并且常量池、静态成员变量等迁移到了堆中;元空间不在虚拟机内存中,而是放在本地内存中。
    类的静态变量和Interned Strings 都被转移到了java堆区,
    只有class元数据才在元空间。

    正如预测metadata是一件纷繁复杂的事情那样,JAVA 8移除了Permanent区,换作Metaspace。从那时起,绝大多数复杂的事情都被移到Java heap区。

    类定义文件,现在都存入叫做“Metaspace”的区域中。他相当于本地内存的一块区域。理论上,Metaspace尺寸仅仅受限于JAVA进程可获得本地内存大小。将JAVA开发人员从仅仅在应用多增加一个类就造成java.lang.OutOfMemoryError: Permgen space的困境中解脱出来。需要注意的是这个看起来不受限制没有损失的空间-让Metaspace无限制的增长你会引起内存重交换或者/和本地内存分配失败。

    某些场合你希望保护自己,你可以如下图所示限制Metaspace增长,Metaspace尺寸限制在265M:

    java -XX:MaxMetaspaceSize=256m
    

    三. 栈配置:

    栈是每个线程私有的内存空间。在java虚拟机中可以使用-Xss指定每个线程的栈大小。

    • 通常只有几百K。
    • 决定了函数调用的深度。
    • 每个线程都有独立的栈空间。
    • 局部变量,参数分配在栈上
      如果想开更多的线程就要减少-Xss的值。因为这样每个线程占用的空间就小了,有限的内存就能容纳更多的线程。

    四.直接内存配置:

    NIO在被广泛使用后,直接内存的使用变得非常普遍。直接内存跳过了java堆,使java程序可以直接访问原生堆空间,因此,从一定程度上加快了内存空间的访问速度。
    最大可以直接内存可以使用参数-XX:MaxDirectMemorySize设置,如果不设置,默认是最大堆空间,即-Xmx。当直接内存使用量达到-XX:MaxDirectMemorySize
    时,就会触发垃圾回收,如果垃圾回收不能有效释放足够空间,直接内存依然会引起系统的OOM。
    一般来说,直接内存的访问速度(读或者写)会快于堆内存。

    /**
     *
     * 直接内存配置:
     * NIO在被广泛使用后,直接内存的使用变得非常普遍。直接内存跳过了java堆,使java程序可以直接访问原生堆空间,因此,从一定程度上加快了内存空间的访问速度。
     * 一般来说,直接内存的访问速度(读或者写)会快于堆内存。下面的代码统计了对直接内存和堆内存的读写速度。
     * @author chenyang30
     * @date 2018/7/14
     */
    public class AccessDirectBuffer {
        public void directAccess(){
            long starttime=System.currentTimeMillis();
            ByteBuffer b=ByteBuffer.allocateDirect(500);
            for(int i=0;i<100000;i++){
                for(int j=0;j<99;j++){
                    b.putInt(j);
                }
                b.flip();
                for(int j=0;j<99;j++){
                    b.getInt();
                }
                b.clear();
            }
            long endtime=System.currentTimeMillis();
            System.out.println("testDirectWrite:"+(endtime-starttime));
        }
    
        public void bufferAccess(){
            long starttime=System.currentTimeMillis();
            ByteBuffer b=ByteBuffer.allocate(500);
            for(int i=0;i<100000;i++){
                for(int j=0;j<99;j++){
                    b.putInt(j);
                }
                b.flip();
                for(int j=0;j<99;j++){
                    b.getInt();
                }
                b.clear();
            }
            long endtime=System.currentTimeMillis();
            System.out.println("testBufferWrite:"+(endtime-starttime));
        }
    
    
        public void directAllocate(){
            long starttime=System.currentTimeMillis();
            for(int i=0;i<200000;i++){
                ByteBuffer b=ByteBuffer.allocateDirect(1000);
            }
    
            long endtime=System.currentTimeMillis();
            System.out.println("directAllocation:"+(endtime-starttime));
        }
    
    
        public void bufferAllocate(){
            long starttime=System.currentTimeMillis();
            for(int i=0;i<200000;i++){
                ByteBuffer b=ByteBuffer.allocateDirect(1000);
            }
    
            long endtime=System.currentTimeMillis();
            System.out.println("bufferAllocation:"+(endtime-starttime));
        }
        public static void main(String[] args) {
            AccessDirectBuffer alloc=new AccessDirectBuffer();
            alloc.bufferAccess();
            alloc.directAccess();
    
            alloc.bufferAccess();
            alloc.directAccess();
    
            alloc.bufferAllocate();
            alloc.directAllocate();
    
            alloc.bufferAllocate();
            alloc.directAllocate();
        }
    }
    

    结果:
    testBufferWrite:40
    testDirectWrite:26
    testBufferWrite:16
    testDirectWrite:11
    bufferAllocation:156
    directAllocation:149
    bufferAllocation:359
    directAllocation:197

    根据耗时可以看出直接内存的访问和空间申请比堆内存快40%左右。

    相关文章

      网友评论

          本文标题:JVM学习(4)非堆的配置参数

          本文链接:https://www.haomeiwen.com/subject/kocypftx.html