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

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

一.元数据区配置(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)非堆的配置参数)