1. 栈、堆、方法区的交互关系
从线程共享与否的角度来看:
交互关系:
2. 方法区的理解
2.1 官方文档
The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the “text” segment in an operating system process. It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods (§2.9) used in class and instance initialization and interface initialization.
The method area is created on virtual machine start-up. Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it. This specification does not mandate the location of the method area or the policies used to manage compiled code. The method area may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the method area, as well as, in the case of a varying-size method area, control over the maximum and minimum method area size.
The following exceptional condition is associated with the method area:
If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an OutOfMemoryError.
参考 docs.oracle.com/javase/spec…
2.2 方法区在哪里
《Java虚拟机规范》中明确说明:”尽管所有的方法区在逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。“但对于HotSpot JVM而言,方法区还有一个别名叫做Non-Heap
(非堆),目的就是要和堆分开
所以,方法区看做是一块独立于Java堆的内存空间
2.3 方法区的基本理解
-
方法区和Java堆一样,是各个线程共享的内存区域
-
方法区在JVM启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的
-
方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展
-
方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误:
java.lang.OurOfMemoryError: PermGen space
或者java.lang.OurOfMemoryError: Metaspace
- 加载大量的第三方jar包
- Tomcat部署的工程过多(30-50)
- 大量动态生成反射类
例如下列简单代码:
package com.nasuf.jvm; public class MethodAreaTest { public static void main(String[] args) { System.out.println("testing"); try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); } } } 复制代码
执行后使用
jvisualvm
查看类加载数量:
可以看到加载了1530
个类而且数量还在动态变化中 -
关闭JVM就会释放这个区域的内存
2.4 HotSpot中方法区的演进
- 在JDK7及以前,习惯上把方法区称为永久代。JDK8开始,使用元空间取代了永久代
In JDK8, classes metadata is now stored in the native heap and this space is called Metaspace.
- 在本质上,方法区和永久代并不等价。仅是对HotSpot虚拟机而言的。《Java虚拟机规范》对如何实现方法区,不做统一要求。例如:BEA JRockit / IBM J9中不存在永久代的概念
- 现在看来,当年使用永久代,并不是好的选择。导致Java程序更容易OOM (超过
-XX:MaxPermSize
上限)
- 现在看来,当年使用永久代,并不是好的选择。导致Java程序更容易OOM (超过
- 而到了JDK8, 终于完全废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间来代替
- 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代最大的区别在于:元空间不在虚拟机设置的内存中,而是使用本地内存
- 永久代、元空间二者并不只是名字变了,内部结构也调整了
- 根据《Java虚拟机规范》规定,如果方法区无法满足新的内存分配需求时,将抛出OOM异常
java.lang.OurOfMemoryError: Metaspace
3. 设置方法区大小与OOM
- 方法区的大小不必是固定的,JVM可以根据应用的需要动态调整
- JDK7及以前:
- 通过
-XX:PermSize
来设置永久代初始分配空间。默认值是20.75M
- 通过
-XX:MaxPermSize
来设置永久代最大可分配空间。32位机器默认是64M
,64位机器默认是82M
- 当JVM加载的类信息容量超过了这个值,会报异常
java.lang.OurOfMemoryError: PermGen space
- 通过
- JDK8及以后:
-
元数据区大小可以使用参数
-XX:MetaspaceSize
和-XX:MaxMetaspaceSize
指定,替代上述原有的两个参数-XX:MaxPermSize=size Sets the maximum permanent generation space size (in bytes). This option was deprecated in JDK 8, and superseded by the -XX:MaxMetaspaceSize option.
-XX:PermSize=size Sets the space (in bytes) allocated to the permanent generation that triggers a garbage collection if it is exceeded. This option was deprecated un JDK 8, and superseded by the -XX:MetaspaceSize option.
-
默认值依赖于平台。windows下,
-XX:MetaspaceSize
是21M
,-XX:MaxMetaspaceSize
的值是-1
,即没有限制 -
与永久代不同,如果不指定大小,默认情况下,虚拟机会耗尽所有可用系统内存。如果元数据区发生溢出,虚拟机一样会抛出异常
java.lang.OurOfMemoryError: Metaspace
-
-XX:MetaspaceSize
设置初始的元空间大小。对于一个64位的服务器端JVM来说,其默认的-XX:MetaspaceSize
值为21M
,这就是初始的高水位线,一旦触及这个水位线,Full GC将会被触发并写在没用的类(即这些类对应的类加载器不在存活),然后这个高水位线将会重置。新的高水位线的值取决于GC后释放了多少元空间。如果释放的空间不足,那么在不超过MaxMetaspaceSize
时,适当提高该值。如果释放空间过多,则适当降低该值 -
如果初始化的高水位线设置过低,上述高水位线调整情况会发生很多次。通过垃圾回收器的日志可以观察到Full GC多次调用。为了避免频繁地GC,建议将
-XX:MetaspaceSize
设置为一个相对较高的值
-