@boothsun
2018-03-11T22:16:13.000000Z
字数 4053
阅读 1479
JVM
JVM堆内存管理机制说到底就是为了解决两个问题:给对象分配内存以及回收分配给对象的内存。
Java Heap被分为两部分:Young Generation 和 Old Gereration。Perm并不属于Heap。
所有的new出来的对象都放在Young Gen,当Young Gen满了, 就会执行Garbage Collection (GC), 此时的GC称为Minor GC。 Young Gen被分成三部分:Eden Memory和两个Survivor Memory。
-XX:PretenureSizeThreshold
参数,令大于这个设置值的对象直接进老年代分配,这样做的目的是避免在Eden区以及两个Survivor区之间发生大量的内存拷贝。-XX:MaxTenuringThreshold
来设置。绝大部分 Java 程序员应该都见过 "java.lang.OutOfMemoryError: PermGen space "这个异常。这里的 “PermGen space”其实指的就是方法区。不过方法区和“PermGen space”又有着本质的区别。前者是 JVM 的规范,而后者则是 JVM 规范的一种实现,并且只有 HotSpot 才有 “PermGen space”,而对于其他类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并没有“PermGen space”。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。最典型的场景就是,在 jsp 页面比较多的情况,容易出现永久代内存溢出。我们现在通过动态生成类来模拟 “PermGen space”的内存溢出:
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
public class PermGenOomMock{
public static void main(String[] args) {
URL url = null;
List<ClassLoader> classLoaderList = new ArrayList<>();
try {
url = new File("/tmp").toURI().toURL();
URL[] urls = {url};
while (true){
ClassLoader loader = new URLClassLoader(urls);
classLoaderList.add(loader);
loader.loadClass("java.lang.Object");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.concurrent.locks.ReentrantLock.<init>(ReentrantLock.java:262)
at java.util.concurrent.ConcurrentHashMap$Segment.<init>(ConcurrentHashMap.java:425)
at java.util.concurrent.ConcurrentHashMap.<init>(ConcurrentHashMap.java:825)
at java.util.concurrent.ConcurrentHashMap.<init>(ConcurrentHashMap.java:869)
at java.lang.ClassLoader.<init>(ClassLoader.java:281)
at java.lang.ClassLoader.<init>(ClassLoader.java:334)
at java.security.SecureClassLoader.<init>(SecureClassLoader.java:99)
at java.net.URLClassLoader.<init>(URLClassLoader.java:140)
at com.boothsun.jvm.PermGenOomMock.main(PermGenOomMock.java:17)
本例中使用的 JDK 版本是 1.7,指定的 PermGen 区的大小为 8M。通过每次生成不同URLClassLoader对象来加载Test类,从而生成不同的类对象,这样就能看到我们熟悉的 "java.lang.OutOfMemoryError: PermGen space " 异常了。这里之所以采用 JDK 1.7,是因为在 JDK 1.8 中, HotSpot 已经没有 “PermGen space”这个区间了,取而代之是一个叫做 Metaspace(元空间) 的东西。下面我们就来看看 Metaspace 与 PermGen space 的区别。
JDK1.8 永久代变化如下图:
元空间时方法区的具体实现,方法区主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常称为“非堆”。
元空间的本地和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。 理论上取决于32位/64位系统可虚拟的内存大小。可见也不是无限制的,需要配置参数。
MetaspaceSize
初始化的Metaspace大小,控制元空间发生GC的阈值。GC后,动态增加或降低MetaspaceSize。在默认情况下,这个值大小根据不同的平台在12M到20M浮动。
MaxMetaspaceSize
限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。在本机上该参数的默认值为4294967295B(大约4096MB)。
MinMetaspaceFreeRatio
当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数(即实际非空闲占比过大,内存不够用),那么虚拟机将增长Metaspace的大小。默认值为40,也就是40%。设置该参数可以控制Metaspace的增长的速度,太小的值会导致Metaspace增长的缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存。
MaxMetasaceFreeRatio
当进行过Metaspace GC之后, 会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间。默认值为70,也就是70%。
MaxMetaspaceExpansion
Metaspace增长时的最大幅度。在本机上该参数的默认值为5452592B(大约为5MB)。
MinMetaspaceExpansion
Metaspace增长时的最小幅度。在本机上该参数的默认值为340784B(大约330KB为)。