小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
之前我们已经了解过“运行时数据区”的程序计数器、虚拟机栈、本地方法栈和堆空间,今天我们就来了解一下最后一个模块——方法区。
简介
创建对象时内存分配简图
《Java虚拟机规范》中明确说明:“尽管所有的方法区在逻辑上属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。”
虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。所以,方法区可以看作是一块独立于 Java 堆的内存空间。
方法区与 Java 堆一样,是各个线程共享的内存区域。方法区在 JVM 启动时就会被创建,并且它的实际的物理内存空间是可以不连续的,关闭 JVM 就会释放这个区域的内存。
永久代、元空间
《java虚拟机规范》对如何实现方法区,不做统一要求。例如:BEA JRockit/IBM J9 中不存在永久代的概念。而对于 HotSpot 来说,在 jdk7 及以前,习惯上把方法区的实现称为永久代,而从 jdk8 开始,使用元空间取代了永久代。
方法区是 Java 虚拟机规范中的概念,而永久代和元空间是 HotSpot 虚拟机对方法区的一种实现。通俗点讲:如果把方法区比作接口的话,那永久代和元空间可以比作实现该接口的实现类。
直接内存
永久代、元空间并不只是名字变了,内部结构也进行了调整。永久代使用的是 JVM 的内存,而元空间使用的是本地的直接内存。
直接内存并不是 JVM 运行时数据区的一部分,因此不会受到 Java 堆的限制。但是它会受到本机总内存大小以及处理器寻址空间的限制,所以如果这部分内存也被频繁的使用,依然会导致 OOM 错误的出现。
方法区的大小
方法区的大小是可以进行设置的,可以选择固定大小也可以进行扩展。
jdk7 及以前
-XX:PermSize=N //方法区 (永久代) 初始分配空间,默认值为 20.75M
-XX:MaxPermSize=N //方法区 (永久代) 最大可分配空间。32位机器默认是64M,64位机器默认是82M
jdk8及以后
默认值依赖于平台,windows下:
-XX:MetaspaceSize=N //方法区 (元空间) 初始分配空间,如果未指定此标志,则元空间将根据运行时的应用程序需求动态地重新调整大小。
-XX:MaxMetaspaceSize=N //方法区 (元空间) 最大可分配空间,默认值为 -1,即没有限制
与永久代很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。
方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,比如:加载大量的第三方 jar 包、Tomcat 部署的工程过多、大量动态生成反射类等都会导致方法区溢出,抛出内存溢出错误。
- 永久代:OutOfMemoryError:PermGen space
- 元空间:OutOfMemoryError:Metaspace
至于如何解决 OOM 异常,将在以后的文章中讲解!
jvisualvm
我们可以通过 JDK 自带的 jvisualvm 工具来查看程序加载的类文件:
例
public class MethodAreaDemo1 {
public static void main(String[] args) {
System.out.println("start...");
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
}
}
运行程序,可以看到一个简单的程序就需要加载这么多的类文件。
高水位线
对于一个64位的服务器端 JVM 来说,XX:MetaspaceSize=21 就是初始的高水位线,一旦触及这个水位线,Full GC 将会被触发并卸载没用的类(即这些类对应的类加载器不再存活),然后这个高水位线将会重置。
新的高水位线的值取决于 GC 后释放了多少元空间:
- 如果释放的空间不足,那么在不超过 MaxMetaspaceSize 时,适当提高该值;
- 如果释放空间过多,则适当降低该值。
如果初始化的高水位线设置过低,高水位线调整情况会发生很多次。通过垃圾回收器的日志可以观察到 Full GC 多次调用。为了避免频繁地GC,建议将
-XX :MetaspaceSize设置为一个相对较高的值。
了解了什么是永久代、元空间之后,下文我们将继续深入,来说一下它的内部结构。如果你有不同的意见或者更好的idea,欢迎联系阿Q,添加阿Q可以加入技术交流群参与讨论呦!