JVM由三个主要的子系统构成
-
-
负责加载class文件,class文件在文件开头有特定的文件标识,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由执行引擎决定;
-
运行时数据区(内存结构)
-
1.方法区(Method Area)
类的所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在这里定义。简单来说,所有定义的方法的 信息都保存在该区域,静态变量+常量+类信息(构造方法/接口定义)+运行时常量池都存在方法区中,虽然Java虚拟 机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是为了和Java的 堆区分开;
-
-
运行时常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分;Class文件除了有类的版本,字段,方法,接口等描述信息外,还有一项存放编译器生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中;当常量池无法再申请到内存时会抛出OutOfMemoryError异常;
-
2.堆(Heap)
虚拟机启动时自动分配创建,用于存放对象的实例,几乎所有对象(包括常量池)都在堆上分配内存,当对象无法在该空间申请到内存是将抛出OutOfMemoryError异常。同时也是垃圾收集器管理的主要区域;
新生代 : 类出生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。 新生代分为两部分:Eden space 和 Survivor space,所有的类都是在Eden区被new出来的;Survivor 区又分为From和To区;当Eden区的空间用完时,程序又需要创建对象,JVM的垃圾回收器将Eden区进行垃圾回收(Minor GC),将Eden区中的不再被其它对象引用的对象进行销毁,然后将Eden区中剩余的对象移到Survivor From区;若 Survivor From 区也满了,再对该区进行垃圾回收,然后移动到Survivor To区;
老年代:新生代经过多次GC仍然存在的对象移动到老年代;若老年代也满了,这时候将发生Major GC(也可以叫Full GC),进行老年区的内存清理;若老年区执行了Full GC之后发现依然无法进行对象的保存,就会抛出OOM(OutOfMemoryError)异常;
元空间:在JDK1.8之后,元空间替代了永久代,它是对JVM规范中方法区的实现,区别在于元数据区不在虚拟机当中,而是用的本地内存,永久代在虚拟机当中,永久代逻辑结构上也属于堆,但是物理上不属于 ;
为什么移除了永久代?参考官方解释http://openjdk.java.net/jeps/122
3.栈(Stack)
虚拟机栈,Java线程执行方法的内存模型,一个线程对应一个栈,每个方法在执行的同时都会创建一个栈帧(用于存储局部变量表,操作数栈,动态链接,方法出口等信息)不存在垃圾回收问题,只要线程一结束该栈就释放,生命周期和线程一致 ;
4.本地方法栈(Native Method Stack)
本地方法栈和虚拟机栈作用很相似,区别不过是虚拟机栈为JVM执行Java方法(也就是字节码)服务,而本地方法栈为虚拟机执行Navtive方法服务;Navtive 方法是 Java 通过 JNI 直接调用本地 C/C++ 库(可以认为是 Native 方法相当于 C/C++ 暴露给 Java 的一个接口,Java 通过调用这个接口从而调用到 C/C++ 方法);
当一个线程调用一个本地方法时,本地方法又回调虚拟机中的另一个Java方法;上图展示了java虚拟机内部线程运行,一个线程可能在整个生命周期中都执行Java方法,操作它的Java栈;或者它可能毫无障碍地在虚拟机栈和本地方法栈之间跳转;
5.程序计数器 (Program Counter Register )
程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器(像一个指针);字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的的字节码指令,它是程序控制流的指示器,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成;
- 执行引擎
-
- 执行引擎负责解析命令,提交操作系统执行
运行时数据区的虚拟机栈,本地方法栈,程序计数器都是线程私有,而方法区和堆是线程共享;