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