[关闭]
@boothsun 2018-03-11T14:16:13.000000Z 字数 4053 阅读 1333

Java堆内存管理

JVM


JVM堆内存管理机制说到底就是为了解决两个问题:给对象分配内存以及回收分配给对象的内存。

Java Heap被分为两部分:Young Generation 和 Old Gereration。Perm并不属于Heap。

Young Generation (Young Gen)

所有的new出来的对象都放在Young Gen,当Young Gen满了, 就会执行Garbage Collection (GC), 此时的GC称为Minor GC。 Young Gen被分成三部分:Eden Memory和两个Survivor Memory。

Old Generation(Tenured Gen)

  1. 回收机制:采用标记压缩算法回收垃圾。
  2. 对象来源:
    • 大对象直接进入老年代
    • Young代中生存时间长的可达对象。
  3. 回收频率:因为很少对象会死掉,所以执行频率不高,而且需要较长时间来完成。

Permanent Generation(永久代)

绝大部分 Java 程序员应该都见过 "java.lang.OutOfMemoryError: PermGen space "这个异常。这里的 “PermGen space”其实指的就是方法区。不过方法区和“PermGen space”又有着本质的区别。前者是 JVM 的规范,而后者则是 JVM 规范的一种实现,并且只有 HotSpot 才有 “PermGen space”,而对于其他类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并没有“PermGen space”。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。最典型的场景就是,在 jsp 页面比较多的情况,容易出现永久代内存溢出。我们现在通过动态生成类来模拟 “PermGen space”的内存溢出:

  1. import java.io.File;
  2. import java.net.URL;
  3. import java.net.URLClassLoader;
  4. import java.util.ArrayList;
  5. import java.util.List;
  6. public class PermGenOomMock{
  7. public static void main(String[] args) {
  8. URL url = null;
  9. List<ClassLoader> classLoaderList = new ArrayList<>();
  10. try {
  11. url = new File("/tmp").toURI().toURL();
  12. URL[] urls = {url};
  13. while (true){
  14. ClassLoader loader = new URLClassLoader(urls);
  15. classLoaderList.add(loader);
  16. loader.loadClass("java.lang.Object");
  17. }
  18. } catch (Exception e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }

运行结果:

  1. Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  2. at java.util.concurrent.locks.ReentrantLock.<init>(ReentrantLock.java:262)
  3. at java.util.concurrent.ConcurrentHashMap$Segment.<init>(ConcurrentHashMap.java:425)
  4. at java.util.concurrent.ConcurrentHashMap.<init>(ConcurrentHashMap.java:825)
  5. at java.util.concurrent.ConcurrentHashMap.<init>(ConcurrentHashMap.java:869)
  6. at java.lang.ClassLoader.<init>(ClassLoader.java:281)
  7. at java.lang.ClassLoader.<init>(ClassLoader.java:334)
  8. at java.security.SecureClassLoader.<init>(SecureClassLoader.java:99)
  9. at java.net.URLClassLoader.<init>(URLClassLoader.java:140)
  10. 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 的区别。

MetaSpace(元空间)

JDK1.8 永久代的废弃

JDK1.8 永久代变化如下图:

  1. 新生代:Eden + From Survivor + To Survivor
  2. 老年代:OldGen
  3. 永久代(方法区的实现):PermGen ---> 替换为Metaspace(本地内存中)

移除永久代的原因

  1. 移除永久代是为融合HotSpot JVM与JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。
  2. 永久代大小不确定,PermSize指定的太小很容易造成永久代OOM,因为PermSize的大小很依赖于很多因素,比如JVM加载的class总数,常量池的大小,方法的大小等。

元空间的内存大小

元空间时方法区的具体实现,方法区主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常称为“非堆”。

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

常用配置参数

  1. MetaspaceSize
    初始化的Metaspace大小,控制元空间发生GC的阈值。GC后,动态增加或降低MetaspaceSize。在默认情况下,这个值大小根据不同的平台在12M到20M浮动。

  2. MaxMetaspaceSize
    限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。在本机上该参数的默认值为4294967295B(大约4096MB)。

  3. MinMetaspaceFreeRatio
    当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数(即实际非空闲占比过大,内存不够用),那么虚拟机将增长Metaspace的大小。默认值为40,也就是40%。设置该参数可以控制Metaspace的增长的速度,太小的值会导致Metaspace增长的缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存。

  4. MaxMetasaceFreeRatio
    当进行过Metaspace GC之后, 会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间。默认值为70,也就是70%。

  5. MaxMetaspaceExpansion
    Metaspace增长时的最大幅度。在本机上该参数的默认值为5452592B(大约为5MB)。

  6. MinMetaspaceExpansion
    Metaspace增长时的最小幅度。在本机上该参数的默认值为340784B(大约330KB为)。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注