JVM是Java Virtual Machine(Java虚拟机)的缩写。html
主要分为五大模块:类装载器子系统、运行时数据区、执行引擎、本地方法接口和垃圾收集模块。java
线程私有,与线程生命周期相同。不连续的内存空间。算法
它描述的是Java方法执行的内存模型:方法建立时会建立一个栈帧,用于存储局部变量表,操做栈,动态连接,方法出口等信息。每个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。编程
局部变量表:是一组变量存储空间,用于存放方法参数和方法内部的局部变量,即八大基础数据类型和引用类型(指向对象地址的引用指针)。数组
操做栈:其结构同局部变量表,也是一个以字节长度为单位的数组。不一样的是,局部变量表经过索引进行访问,而操做栈则是经过压栈--出栈完成的。其中存储数据的方式也和局部变量表同样,如int、long、float、double、reference和returnType的存储。对于byte、short以及char类型的值在压入到操做数栈以前,也会被转换为int。虚拟机把操做数栈做为它的工做区——大多数指令都要从这里弹出数据,执行运算,而后把结果压回操做数栈。安全
动态连接:虚拟机运行时,运行时常量池会保存大量的符号引用,此期间将符号引用转换成直接引用,以访问到真正的方法或对象,称为动态连接。数据结构
做用与虚拟机栈相似,区别在于:虚拟机栈为虚拟机执行的.class文件服务,本地方法栈则为虚拟机使用到的Native方法(一个java调用非Java代码的接口)服务。和虚拟机栈同样,也会抛出栈溢出(StackOverflowError)和堆溢出(OutOfMemoryError)。多线程
全部线程共有,在虚拟机启动时建立。分为两个部分:年轻代和老年代。主要用于存放对象实例,几乎全部的对象实例都在这里分配内存,是垃圾收集器管理的主要区域,有时也被称为“GC”堆(Garbage Collected Heap)。jvm
年轻代:没有经历过屡次GC回收。分为一个Eden区和两个Survivor区,对象优先在Eden区分配。年轻代也称新生代,这里进行的垃圾回收也称新生代GC(Minor GC),主要采用的是复制算法,由于这里的GC进行比较频繁,在通过屡次GC后仍然在Survivor区存在对象,那么就将对象存入到老年代中。
老年代:经历了必定次数的GC回收的年轻代。发生在老年代的GC称为Full GC,又称为Major GC,其常常会伴随至少一次的Minor GC(并不是绝对,在Parallel Scavenge收集器中就有直接进行Full GC的策略选择过程)。Major GC的速度通常会比Minor GC慢10倍以上。这里使用的GC算法是标记整理算法(老年代回收算法)。ide
永久代:用于存储类的信息、编译后的代码数据等。JDK8以前,方法区由永久代来实现,即GC分代收集至方法区,主要针对常量池的回收和类型的卸载。这样HotSpot的垃圾收集器能够像管理Java堆同样管理这部份内存,能省去专门为方法区编写内存管理代码的工做。
jdk8后,永久代被元数据区(元空间 Metaspace)替代,jdk8以前永久代(Perm)全部内容的字符串常量移至堆内存,其余内容移至元空间,相比JDK8以前的永久代,元空间直接在本地内存分配,并不在虚拟机中。
元空间在逻辑上属于堆区,但为了与堆进行区分,也叫作非堆。
详细参考:https://blog.csdn.net/weixin_40739833/article/details/80717638
主要用于存储已被虚拟机加载的类信息(类的描述信息,全限定名、直接超类的全限定名、类的类型仍是接口类型、访问修饰符、直接超接口的全限定名的有序列表)、静态变量、即时编译器编译后的代码、常量池(运行时、静态常量池)等。
也是各个线程共享的区域。该区域不多发生垃圾回收,在这里进行的GC主要是对方法区里的常量池和类型的卸载。
静态常量池:.class文件中的常量池,主要包含字面量和符号引用量。字面量至关于Java语言层面常量的概念,如文本字符串,声明为final的常量值等。符号引用量分为三种:类和接口的全限定名、字段名称和描述符、方法名称和描述符。当所引用的类被加载后,它的符号引用将变成直接引用。
运行时常量池:类装载完成后,将class文件中的常量池载入到内存中,并保存到方法区中。class文件中的常量池中的内容会在类加载后进入方法区的运行时常量池。相对于常量池,运行时常量池的重要特征是具备动态性,java并不要求常量只有在编译器才会产生,运行期间也能够将新的常量存放入池中,这种特性用的最多的String类中的intern()方法。
是记录当前线程所执行字节码的行号指示器。
内存空间较小,线程私有。经过改变这个计数器的值来选取下一条须要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能。
在多线程环境下,一个程序的运行是经过线程的轮流切换并分配处理器执行时间的方式进行的,为了使线程切换时能恢复到正确的位置,线程须要一个标记,即程序计数器。各线程之间的计数器互不影响,即“线程私有”。
若是线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;若是正在执行的是 Native 方法,这个计数器值则为空(Undefined)。此内存区域是惟一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 状况的区域。
一个Java文件从编码完成到最终执行,通常主要包括两个过程:编译和运行。
编译:即把咱们写好的Java文件,经过Javac命令编译成字节码文件,也就是.class文件。
运行:即把编译好的.class文件交给Java虚拟机(jvm)执行。
类加载过程发生在运行阶段。是Java虚拟机(jvm)把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程。
这个过程呢又分为三个部分,分别是:加载、链接、初始化,其中链接又分为三个小的部分:验证、准备、解析。
①、加载:简单来讲,加载指的是把class字节码文件从各个来源经过类加载器装载入内存中。
Ⅰ、经过类的全限定名获取定义此类的二进制字节流。
Ⅱ、将这个字节流表明的静态存储结构转为方法区的运行时数据结构.
Ⅲ、并在堆内存中生成一个表明这个类的java.lang.class对象,做为访问方法去这些数据结构的入口。
②、链接:
其中包含:
Ⅰ、验证:主要是判断class文件的合法性,对版本号进行验证例如若是使用java1.8编译后的class文件要再java1.6虚拟机上运行),还会对元数据,字节编码等进行验证,确保class文件里的字节流信息符合当前虚拟机的要求,不会危害虚拟机的安全。
Ⅱ、准备:主要是分配内存,为变量分配初始值,即在方法区中为这些变量分配内存空间。
须要注意的是,这时候进行内存分配的仅仅是类变量(也就是被static修饰的变量),实例变量是不包括的,实例变量的初始化是在对象实例化的时候进行初始化,并且分配的内存区域是Java堆。这里的初始值也就是在编程中默认值,也就是零值。
其中static修饰的变量,在准备阶段会被初始化为0,类的初始化阶段才会被赋值。
例:public static int i = 1;在准备阶段i的值会被初始化为0,后面的类的初始化阶段才会赋值为1;
而被static final修饰的变量,在准备阶段就会被赋值。
例:public static final int i = 1;对应常量(static final)i,在准备阶段就会被赋值1;
Ⅲ、解析:
解析就是把类中的符号引用替换为直接引用;例如某个类继承了java.lang.Object,原来的符号引用记录的是“java.lang.Object”,并非java.lang,Object对象,直接引用就是找出对应的java.lang.Object对应的内存地址,创建直接引用关系;
③、初始化:
初始化过程当中,会执行:类的构造函数,static修饰的变量赋值语句,static{}代码块,如有继承关系存在,初始化子类以前,会先初始化父类,就像是儿子在端碗吃饭以前,父亲先演示一遍怎么端碗吃饭。Java中,Java.lang.Object是全部类的超类,因此类初始化以前,Java.lang.Object一定被初始化。
如下状况不会执行类的初始化:
Ⅰ、经过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
Ⅱ、定义对象数组,不会触发该类的初始化。
Ⅲ、常量在编译期间会存入调用类的常量池中,本质上并无直接引用定义常量的类,不会触发定义常量所在的类。
Ⅳ、经过类名获取Class对象,不会触发类的初始化。
Ⅴ、经过Class.forName加载指定类时,若是指定参数initialize为false时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。
Ⅵ、经过ClassLoader默认的loadClass方法,也不会触发初始化动做。
ps:类加载器:详见http://www.javashuo.com/article/p-flqqiuqi-dd.html
BootStrap Classloader(启动Classloader):主要加载JVM自身须要的类。这个加载器由C++编写是虚拟机的一部分,负责加载 JAVA_HOME\lib 目录中的,或经过-Xbootclasspath参数指定路径中的,且被虚拟机承认(按文件名识别,如rt.jar)的类。
Extension Classloader(扩展Classloader):是sun.misc.Launcher中的内部类ExtClassLoader,负责加载 JAVA_HOME\lib\ext 目录中的,或经过java.ext.dirs系统变量指定路径中的类库。
ClassLoader(应用Classloader):是sun.misc.Launcher中的内部类AppClassLoader,负责加载用户路径上的类库。用户也能够经过继承ClassLoader实现本身的类加载器。
双亲委派模式:当一个Hello.class这样的文件要被加载时。不考虑咱们自定义类加载器,首先会在AppClassLoader中检查是否加载过,若是有那就无需再加载了。若是没有,那么会拿到父加载器,而后调用父加载器的loadClass方法。父类中同理会先检查本身是否已经加载过,若是没有再往上。注意这个过程,知道到达Bootstrap classLoader以前,都是没有哪一个加载器本身选择加载的。若是父加载器没法加载,会下沉到子加载器去加载,一直到最底层,若是没有任何加载器能加载,就会抛出ClassNotFoundException。
优势:避免一个类被重复加载。
注意:即便两个类来源于相同的class文件,若是使用的类加载器不一样,加载后的对象时彻底不一样的,这个不一样反应在对象的 equals()、isAssignableFrom()、isInstance()等方法的返回结果,也包括了使用 instanceof 关键字对对象所属关系的断定结果。
缺点:顶层ClassLoader,没法加载底层ClassLoader的类。如:
javax.xml.parsers包中定义了xml解析的类接口,Service Provider Interface SPI 位于rt.jar ,即接口在启动ClassLoader中。而SPI的实现类,在AppLoader这样就没法用BootstrapClassLoader去加载SPI的实现类。
解决办法:基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例。即双亲委派模式的破坏。
初始化阶段完成,类的加载完成。
④、使用。
⑤、卸载:当该类的class对象再也不被引用后(即便反射也不能获取该类的对象的引用时)、当该类的类加载器ClassLoader被回收时、当该类的全部实例被回收时,以上条件所有知足时,该类在方法区进行垃圾回收时会被jvm卸载,相似在方法区清空类的信息。
由Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载,用户自定义的加载器所加载的类会被卸载。
(多方整理收集而成,侵删)
如有不正之处,欢迎指出。