在稍微了解Java内存分区的时候,大多数文章都是出自深入理解jvm这本书,上来就是给你分了 程序计数器,Java虚拟机栈,本地方法栈,堆,方法区,还有个直接内存,还说方法区里面有个常量池。在写这本书的时候,jdk还在1.6,但是现在2020年jdk都已经jdk14了,虽然还没普及jdk14,但是估计以后都会使用的吧,就像现在基本最低都要使用jdk1.8一样。1.7是在2011年发布的,1.8是14年发布的,时至今日,内存分区已经有了些许变化。虽然理论还是大差不差的。
永久代主要存放以下数据:
从 JDK7 开始,JDK 开发者们就有消灭永久代的打算了。有部分数据移到永久代之外了:
到了 JDK8,这个工作终于完成了,彻底废弃了 PermGen,Metaspace 取而代之。
根据上面的各种原因,永久代最终被移除,方法区移至Metaspace,字符串常量移至Java Heap(待测试确认)。
Metaspace 区域位于堆外,所以它的最大内存大小取决于系统内存,而不是堆大小,我们可以指定 MaxMetaspaceSize 参数来限定它的最大内存。
Metaspace 是用来存放 class metadata 的,class metadata 用于记录一个 Java 类在 JVM 中的信息,包括但不限于 JVM class file format 的运行时数据:
1、Klass 结构,这个非常重要,把它理解为一个 Java 类在虚拟机内部的表示吧;
2、method metadata,包括方法的字节码、局部变量表、异常表、参数信息等;
3、常量池;
4、注解;
5、方法计数器,记录方法被执行的次数,用来辅助 JIT 决策
6、 其他
虽然每个 Java 类都关联了一个 java.lang.Class 的实例,而且它是一个贮存在堆中的 Java 对象。但是类的 class metadata 不是一个 Java 对象,它不在堆中,而是在 Metaspace 中。
当一个类被加载时,它的类加载器会负责在 Metaspace 中分配空间用于存放这个类的元数据
分配给一个类的空间,是归属于这个类的类加载器的,只有当这个类加载器卸载的时候,这个空间才会被释放。
所以,只有当这个类加载器加载的所有类都没有存活的对象,并且没有到达这些类和类加载器的引用时,相应的 Metaspace 空间才会被 GC 释放。
-XX:MaxMetaspaceSize
:Metaspace 总空间的最大允许使用内存,默认是不限制。
-XX:CompressedClassSpaceSize
:Metaspace 中的 Compressed Class Space 的最大允许内存,默认值是 1G,这部分会在 JVM 启动的时候向操作系统申请 1G 的虚拟地址映射,但不是真的就用了操作系统的 1G 内存。
Metaspace 只在 GC 运行并且卸载类加载器的时候才会释放空间。当然,在某些时候,需要主动触发 GC 来回收一些没用的 class metadata,即使这个时候对于堆空间来说,还达不到 GC 的条件。
Metaspace 可能在两种情况下触发 GC:
1、分配空间时:虚拟机维护了一个阈值,如果 Metaspace 的空间大小超过了这个阈值,那么在新的空间分配申请时,虚拟机首先会通过收集可以卸载的类加载器来达到复用空间的目的,而不是扩大 Metaspace 的空间,这个时候会触发 GC。这个阈值会上下调整,和 Metaspace 已经占用的操作系统内存保持一个距离。
2、碰到 Metaspace OOM:Metaspace 的总使用空间达到了 MaxMetaspaceSize 设置的阈值,或者 Compressed Class Space 被使用光了,如果这次 GC 真的通过卸载类加载器腾出了很多的空间,这很好,否则的话,我们会进入一个糟糕的 GC 周期,即使我们有足够的堆内存。
各个jdk的发行时间
从这个表中我们可以看出一个非常有意思的现象,就是JDK的每一个版本号都使用一个开发代号表示(就是表中的中文名)。而且从JDK1.2.2 开始,主要版本(如1.3,1.4,5.0)都是以鸟类或哺乳动物来命名的. 而它们的bug修正版本(如1.2.2,1.3.1,1.4.2)都是以昆虫命名的。
时间-事件轴
1995年5月23日,Java语言诞生
1996年1月,第一个JDK-JDK1.0诞生
1996年4月,10个最主要的操作系统供应商申明将在其产品中嵌入JAVA技术
1996年9月,约8.3万个网页应用了JAVA技术来制作
1997年2月18日,JDK1.1发布
1997年4月2日,JavaOne会议召开,参与者逾一万人,创当时全球同类会议规模之纪录
1997年9月,JavaDeveloperConnection社区成员超过十万
1998年2月,JDK1.1被下载超过2,000,000次
1998年12月8日,JAVA2企业平台J2EE发布
1999年6月,SUN公司发布Java的三个版本:标准版、企业版和微型版(J2SE、J2EE、J2ME)
2000年5月8日,JDK1.3发布
2000年5月29日,JDK1.4发布
2001年6月5日,NOKIA宣布,到2003年将出售1亿部支持Java的手机
2001年9月24日,J2EE1.3发布
2002年2月13日,J2SE1.4发布,自此Java的计算能力有了大幅提升。
2004年9月30日18:00PM,J2SE1.5发布,是Java语言的发展史上的又一里程碑事件。为了表示这个版本的重要性,J2SE1.5更名为J2SE5.0
2005年6月,JavaOne大会召开,SUN公司公开Java SE 6。此时,Java的各种版本已经更名以取消其中的数字“2”:J2EE更名为Java EE, J2SE更名为Java SE,J2ME更名为Java ME。
2006年11月13日,SUN公司宣布Java全线采纳GNU General Public License Version 2,从而公开了Java的源代码。
附2张自己造的图
jdk1.7的时候,一张jvm内存的gc图
这个时候PermGen还是存在的,验证了文章开头的理论。
jdk1.8的时候,一张jvm内存的gc图
配置的jvm的参数;
-XX:+PrintGCDetails -Xms200M -Xmx200M -Xmn10M -XX:SurvivorRatio=8 -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:CompressedClassSpaceSize=5M
看到方法区真的就不在了,被Metaspace区替代了。也验证文章开头的理论。
参考:
深入理解堆外内存 Metaspace
Metaspace 之一:Metaspace整体介绍(永久代被替换原因、元空间特点、元空间内存查看分析方法)