方法区
栈,堆,方法区的交互关系:
方法区的理解
方法区看作是一块独立于java堆的内存空间。(关系如台湾与中国)
方法区与java堆一样,是各个线程共享的内存区域。
方法去在JVM启动时被创建,并且和java堆一样在物理地址是可以不连续的。关闭时JVM释放这块区域内存。
方法区的大小和堆一样,可以选择固定或者可扩展。
方法区的大小决定了可以保存多少个类,如果系统定义过多类,会导致方法区移除。
jdk7及以前把方法区称为永久代,jdk8及以后使用元空间取代永久代。
元空间与永久代的区别:元空间使用本地主机内存。
设置方法区的大小与OOM
jdk7及以前:
-XX:PermSize来设置永久代的初始分配空间。默认27.5M
-XX:MaxPermSize来设置永久代的最大可分配空间。32为机器是64M,64为机器是82M。
jdk8及以后:
默认值依赖于平台,-XX:MetaspaceSize是21M,-XX:MaxMetaspaceSize的值是-1,没有限制。
初始内存空间大小称为高水位线
,一旦触及这个高水位线,Full GC就会被触发卸载一些没用的类,然后高水位线会被重置,重置的大小由gc释放的空间决定。如果高水位线设置过低,就会平凡出发gc,建议设置为相对较高。
内存泄漏与内存溢出
内存溢出就是new的对象太多,而且这些对象都是有用的,不能够被垃圾回收掉。有想要new新的对象,由于堆内存不够,造成OOM。
内存泄露就是对象应该被回收,但是由于GCRoots仍然引用,导致垃圾回收器不能回收,一般而言是代码出现了问题,例如IO流没有关闭、ThreadLocal没有remove。
内存泄露就是存在引用指向已经不会再使用的对象,导致该对象不能被回收。
泄露就是说可使用的内存在逐步减小,溢出就是内存用满了。
方法区内部结构
方法区:它用于存储已被虚拟机加载的类型信息、常量、静态变量、即使编译器编译后的代码缓存。
类型信息:
权限修饰符,全类名,父类全类名(例java.long.Object),实现多个的接口全类名(含泛型信息)。
域(Filed)信息:
域名称,域类型,域修饰符。
方法信息:
构造器信息(从字节码看方法信息中含有构造器),方法名称、返回值类型、方法修饰符、方法参数的数量和类型、方法的字节码、异常表。
构造器:
方法信息:
异常表信息:
静态变量和类关联在一起,随着类的加载而加载,他们成为类数据在逻辑上的一部分。
类变量被类的所有实例共享,即使没有类实例时你也可以访问它。
全局常量(static final)在编译时就被赋值了
常量池与运行时常量池
常量池:包含各种字面量和对类型、域、方法的符号引用。
小结:常量池可以看成一张表,虚拟机指令根据这张表找到要执行的类名、方法名、参数类型、字面量类型
运行时常量池是方法区的一部分。
常量池是class文件的一部分。用于存放编译期生成各种字面量和符号引用
,这部分内容在类加载结束后存放到方法区的运行时常量池中。
JVM为每个已加载的类型(类或接口)都会维护一个常量池。池中的数据像数组一样,通过索引访问。(#3)
运行时常量池另一重要特性:具有动态性
方法去演进细节
只有Hotspot才有永久代。JRockit、IBM不存在永久代概念。
- jdk1.6及以前:用永久代,静态变量存放在永久代上。
- jdk1.7:有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中。
- jdk8及以后:无永久代。类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池、静态变量仍在堆。
为什么要用元空间替换永久代?
1、永久代大小不好设定,小了容易Full GC造成性能降低或出现OOM,太大了造成内存空间浪费。
2、永久代调优比较困难。垃圾回收困难。
StringTable(字符串常量池)为什么要调整?
jdk7放在永久代中,永久代的回收效率很低,仅在Full gc才出发垃圾回收。在开发中会创建大量的字符串,回收效率低,会导致永久代内存不足。放在堆里(年轻代与老年代)回收内存。
方法区的垃圾回收
java虚拟机规范没有给出明确要求是否必须回收方法区。
(Full GC)方法区的垃圾回收:常量池中废弃的常量、不再使用的类型。比较困难。
Hotspot:只要常量池中的常量没有被任何地方引用,就可以回收。
判定类型不在使用比较苛刻:
- 该类的所有实例已经全部被回收,也就是java堆中不存在该类及其任何派生子类的实例。
- 加载该类的类加载器已经被回收。
- 该类对应java.long.Class(大class实例)对象没有在任何被引用过,无法在任何地方通过反射访问该类的方法。