五、本地方法
方法区是独立于堆的一块内存空间
栈、堆、方法区相对于对象创建的关系:
1、概述
- • 方法区与堆一样,是各个线程共享的区域;
- • 方法区在JVM启动的时候被创建,并且它的实际物理内存空间中和堆一样都可以是不连续的;
- • 方法区的大小和堆一样是可以选择固定大小或者可扩展的;
- • 方法区的大小决定了系统可以保存多少个类。如果定义了太多的类,会导致方法区溢出,虚拟机会抛出内存溢出错误;
- •
java.lang.OutOfMemoryError:Metaspace
(JDK8元空间) - •
java.lang.OutOfMemoryError:PermGen Space
(JDK7之前永久代)
- • 关闭虚拟机就会释放这个区域的内存
2、设置方法区内存大小
方法区的大小不是固定的,他可以根据需要动态调整
2.1、JDK7之前
JDK7之前称之为永久代
- • 设置永久代初始分配空间
-XX:PermSize
(默认20.75M) - • 设置永久代最大分配空间
- XX:MaxPermSize
(32位默认为64M,64位默认为82M)
2.2、JDK8之后
JDK8称之为元空间
- • 设置初始元空间大小
-XX:MetaspaceSize
- • 设置最大元空间大小
-XX:MaxMetaspaceSize
- • 与永久代不同,如果不指定大小,默认情况下,虚拟机可能耗尽所有的可用系统内存,然后抛出
OOM
3、方法区存储内容
《深入理解ava虚拟机》书中对方法区(Method Area
)存储的内容如下:它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存。
3.1、类型信息
对每个加载的类型(类class
, 接口interface
, 枚举enum
,注解annotation
) ,jvm必须在方法区中存储以下信息
- 1. 这个类型要有完整有效名称(全名=包名.类名)
- 2. 这个类型直接父类完整有效名(对于
interface
或是java .lang.Object
,都没有父类) - 3. 这个类型的修饰符(
public
,abstract
,final
的某个子集) - 4. 这个类型直接接口的一个有序列表
3.2、域Feild信息
JVM必须在方法区中保存类型中的所有域的相关信息以及类的声明顺序
域的相关信息包括:
- • 域名称
- • 域类型
- • 域修饰符(
public
,private
,protected
,static
,final
,volatile
,transient
的某个子集)
3.3、方法(Method)信息
JVM必须保存方法的以下信息,同域信息一样包括声明顺序
- • 方法名称
- • 方法的返回值类型(包括
void
) - • 方法参数的数量和类型(按顺序)
- • 方法的修饰符(
public
,private
,protected
,static
,final
,synchronize
,native
,abstract
的一 个子集) - • 方法的字节码(
bytecodes
)、 操作数栈, 局部变量表及大小(abstract
和native
方法除外) - • 异常表(
abstract
和native
方法除外)--------每个异常处理的开始位置,结束位置,代码处理在程序计数器中的偏移地址,被捕获的异常类的常量池索引。
3.4、non-final的类变量
- • 静态变量和类关联在一起,随着类的加载而加载,他们成为类数据在逻辑上的一部分
- • 类变量被类的所有实例共享,即便没有实例也可以访问
4、运行时常量池
一个有效的字节码文件中除了包含类的版本信息、字段、方法以及接口等描述信息外,还包含产量池(Constant Pool Table),包含对各种自变量和类型域和方法的符号引用 。
4.1、常量池存储的数据类型
几种在常量池内存储的数据类型包括:
- • 数量值
- • 字符串值
- • 类引用
- • 字段引用
- • 方法引用
可以将常量池看成是一张表,虚拟机指令通过这张表找到要执行的类名、方法名、参数类型、字面量等类型。
4.2、概述
- • 运行时常量池属于方法区的一部分;
- • 常量池表----是Class文件的一部分用于存放编译器生成的各种字面量与符号引用,这部分内容将在类加载后存放在方法区的运行时常量池中;
- • 在加载类和接口到虚拟机中后,就会创建对应的运行时常量池
- • JVM对以加载的类型(类或者接口),都维护一个常量池。池中的数据项就像数组项一样,是通过索引访问的。
- • 当创建类或接口到运行时常量池时,如果构造运行时常量池所需的内存空间超过了方法区所能提供的最大值,则JVM就 会抛出OOM异常。
5、方法区演进
注意:只有HotSpot才有永久代
- •
JDK1.6
之前
- • 有永久代,静态变量存放在永久代上面
- •
JDK1.7
• 有永久代,但是逐步的“去永久代”,字符串常量池,静态变量移除,保存在堆中
•
JDK1.8
• 无永久代,类型信息,字段,方法,常量保存在本地内存的元空间,但是字符串常量池,静态变量仍然在堆中
为什么要把StringTable
进行调整?
JDK1.7
将StringTable
放到了堆空间中,主要原因是永久代的回收效率很低,在full GC
的时候才会被触发,而full GC
是老年代空间不足、永久代空间不足的时候才会触发。这就导致了StringTable
的回收效率不高,而我们开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足,放在堆里能进行及时的回收.
6、方法区的垃圾回收
- • 方法区的垃圾收集主要收集两部分内容:常量池中废弃的常量和不再使用的类型
- •
HotSpot
虚拟机对常量池回收策略是非常明确的,只要常量池中没有被任何地方引用,就可以被回收 - • 回收常量与回收Java堆中的对象非常相似
- • 判断一个常量是否被废弃满足的三个条件
- • 该类的所有实例都已经被回收,也就是java堆中不存在该类及其任何派生子类的实例
- • 加载该类的类加载器已经被回收,这个条件除非精心设计的可替换的类加载器的场景,如
OSGI
,JSP
等的重加载等,否则通常很难达成 - • 该类对应的
java.lang.Class
对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法
注意:
- • Java虚拟机被允许对满足上述三个条件的无用类进行回收,这里说的仅仅是“被允许”,而并不是 和对象一样,没有引用了就必然会回收。
- • 在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载 器的场景中,通常都需要Java虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压力。
7、常见面试题
- • 说一下JVM的内存模型,有哪些区?分别干什么的?
- • java8中内存分代的改进?
- • JVM内存分为哪几个区?每个区的作用是什么?
- • JVM内存分布/内存结构?栈和堆的区别?堆的结构?为什么要有两个
survivor
区? - •
Eden
和Survivor
的比例分配? - • JVM内存分区,为什么要有新生代和老年代?
- • Java的内存分区?
- • 讲讲JVM的运行时数据区?
- • 什么时候对象会进入老年代?