文章目录
1、栈、堆、方法区的交互关系
- 运行时数据结构图:从线程共享与否的角度看
2、方法区的理解
- 方法区与堆空间一样,是各个线程共享的区域
- 方法区是JVM启动时创建的,并且它也是逻辑上连续,物理上不连续。
- 如果你系统家加载了太多的类,导致HotSpot的方法区溢出,虚拟机会抛出OOM:MeatSpace;
- 关闭虚拟机就会释放方法区的内存;
- 到了Java8,HotSpot虚拟机用元空间代替了永久代的概念;元空间使用的是本地内存,如果加载的类太多,本地内存放不下,照样会抛出异常:OOM:MeatSpace;
3、设置方法区大小与OOM
- 方法区初始默认大小是21M
- 最大就是你电脑内存大小
- 设置方法区大小的参数
-XX:MetaspaceSize=100m(初始值设置)
-XX:MaxMetaspaceSize=200m(最大值设置)
举例OOM:
(了解)
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
public class OOMTest extends ClassLoader{
public static void main(String[] args) {
int j=0;
try{
OOMTest test = new OOMTest();
for (int i = 0; i < 10000; i++) {
//创建ClassWriter对象,用于生成类的二进制字节码
ClassWriter classWriter = new ClassWriter(0);
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC,"Class"+i,null,"java/lang/Object",null);
//返回byte
byte[] code = classWriter.toByteArray();
//类的加载
test.defineClass("Class"+i,code,0,code.length);//Class对象
j++;
}
}finally {
System.out.println(j);
}
}
}
如何解决OOM?
在以后篇章会细说!
4、方法区内部结构*
(1)方法区存储些什么?
- 《深入理解Java虚拟机》说,方法区用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等,当然这也不是绝对的,现在jdk更新太快,有一些小的变化,上述描述的是经典版本;
- 方法区还保存着一个类是被哪个加载器加载的一些信息
- 那么方法区还保存着一个加载器加载过哪些类的信息
(2)类型信息
- 对于每加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必须在方法区存储一下信息
①这个类型完整有效名称(全名=包名.类名)
②这个类型直接父类的完整有效名(接口和Object没有父类)
③这个类型的修饰符(public,abstract,final的某个子集)
④这个类型直接接口的有序列表(比如一个类实现了多个接口,那么这些被实现的接口有一个有序的列表)
(3)域(Field)信息
- JVM必须在方法区保存类型的所有域(Field)的相关信息以及域的声明顺序
- 域的相关信息包括:域名称、域类型、域修饰符(public、static、private、protected、final、volatile、transient的某个子集)
volatile:是Java提供的一种轻量级的同步机制
(4)方法(Method)信息
- 方法名称
- 方法返回类型
- 方法参数的数量和类型
- 方法的修饰符(public、private、protected、static、final、synchronized、native、abstract)
- 方法的字节码(bytecode)、操作数栈和局部变量表的大小,(abstract、native)除外;
- 异常表(abstract、native)除外
①每个异常的开始位置、结束位置、代码处理在在程序计数器中的偏移地址;
(5)补充:
1、non-final的类变量
- 静态变量(类变量)是属于类的,随着类的加载而加载;
- 类变量被类的所有实例实例所共享,即使没有实例也可以访问他们:
public class MethodAreaTest {
public static void main(String[] args) {
Order order=null;
System.out.println(order.count);
System.out.println(order.a);
}
}
class Order{
public int count=10;
public static int a=90;
}
上面代码中访问a是90,访问count却报空指针错误
2 、全局常量:static final
每个被声明为final的变量,即static fianl 两个关键词修饰的属性或者只被final修饰的属性,在编译阶段就会初始化
public class MethodAreaTest {
public static void main(String[] args) {
Order order=null;
System.out.println(order.count);
System.out.println(order.a);
}
}
class Order{
public int count=10;
public static int a=90;
public final int b=100;
public static final int c=200;
public static final String d="abcd";
}
可以看出final 修饰的属性在编译期间就已经被初始化了
回顾:static静态变量在类的加载阶段的第二阶段(链接)的准备阶段(prepare)被默认赋值为0,然后第三阶段的初始化阶段进行真正的赋值。
(6)常量池
常量池就像是一张表,JVM指令根据这张表找到要执行的类名、方法名、参数类型、参数名、字面量等类型;
常量池是字节码文件中的一部分!!!
(6)运行时常量池
- 常量池表是Class文件的一部分,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中
- 当加载类或者接口到JVM后,会创建对应的运行时常量池
- JVM为每个已经加载的类型(类或者接口)都会维护一个常量池。池中的数据项(真实地址)像数组项一样通过索引访问(像数组通过下标索引)。
- 运行时常量池存储的不再是常量池中的符号地址了而是真实地址。
5、方法区使用举例
。。。
6、方法区的演进细节
- 首先明确只有Hotspot才有永久代。
3. 为什么要用原空间替代永久代?
答:
①永久代设置空大小很难确定,实际的web工程中,功能点较多,需要动态加载很多类,经常OOM
②对永久代调优很困难。(涉及Full GC<很浪费时间>)
4. Stringtable为什么要调整?
jdk7中将stringTable放到了堆空间中。因为永久代的回收效率很低,在full gc的时候才会触发。而full gc是老年代的空间不足、永久代不足时才会触发。这就导致stringTable回收效率不高。而我们开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足。放到堆里,能及时回收内存。
5. 静态变量存放在哪里?(堆空间)
7、方法区的垃圾回收
- 方法区需不需要回收,《Java虚拟机规范》说,你可以回收,也可以不回收。
- 主要是这个区域的回收效果比较难令人满意,尤其是类型卸载(回收不使用的类),条件相当苛刻,但是回收有时候又有必要;
- 方法区垃圾收集主要两个部分内容,常量池中废弃的常量和不再使用的类型。
8、总结
9、常见面试题