JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是经过在实际的计算机上仿真模拟各类计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。 JVM屏蔽了与具体操做系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就能够在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终仍是把字节码解释成具体平台上的机器指令执行。c++
Java语言的一个很是重要的特色就是与平台的无关性。而使用Java虚拟机是实现这一特色的关键。通常的高级语言若是要在不一样的平台上运行,至少须要编译成不一样的目标代码。而引入Java语言虚拟机后,Java语言在不一样平台上运行时不须要从新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就能够在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的可以“一次编译,处处运行”的缘由。算法
类加载子系统(Class Loader)segmentfault
类加载器分为:自定义类加载器 < 系统类加载器 < 扩展类加载器 < 引导类加载器api
类加载过程分为:加载、连接、验证、初始化。数组
程序计数器(Program Counter Register)安全
是一块较小的内存空间,能够看做是当前线程所执行字节码的行号指示器,指向下一个将要执行的指令代码,由执行引擎来读取下一条指令。网络
虚拟机栈 (Stack Area)数据结构
栈是线程私有,栈帧是栈的元素。每一个方法在执行时都会建立一个栈帧。栈帧中存储了局部变量表、操做数栈、动态链接和方法出口等信息。每一个方法从调用到运行结束的过程,就对应着一个栈帧在栈中压栈到出栈的过程。多线程
本地方法栈 (Native Method Area)
JVM 中的栈包括 Java 虚拟机栈和本地方法栈,二者的区别就是,Java 虚拟机栈为 JVM 执行 Java 方法服务,本地方法栈则为 JVM 使用到的 Native 方法服务。
堆 (Heap Area)
堆是Java虚拟机所管理的内存中最大的一块存储区域。堆内存被全部线程共享。主要存放使用new关键字建立的对象。全部对象实例以及数组都要在堆上分配。垃圾收集器就是根据GC算法,收集堆上对象所占用的内存空间。
Java堆分为年轻代(Young Generation)和老年代(Old Generation);年轻代又分为伊甸园(Eden)和幸存区(Survivor区);幸存区又分为From Survivor空间和 To Survivor空间。
方法区(Method Area)
方法区同 Java 堆同样是被全部线程共享的区间,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码。更具体的说,静态变量+常量+类信息(版本、方法、字段等)+运行时常量池存在方法区中。常量池是方法区的一部分。
JDK 8 使用元空间 MetaSpace 代替方法区,元空间并不在JVM中,而是在本地内存中
类加载器子系统负责从文件系统或者网络中在家Class文件,class文件在文件开头又特定的文件标识。
ClassLoader只负责class文件的加载,至于它是否能够运行,则由ExecutionEngine
决定。
加载类的信息存放于一块被称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
类的加载过程大体分为三个阶段:加载,连接,初始化。
java.lang.Class
对象,做为方法区这个类的各类数据的访问入口验证(Verify)
准备(Prepare)
准备阶段是进行内存分配。为类变量也就是类中由static修饰的变量分配内存,而且设置初始值,这里要注意,初始值是默认初始值0、null、0.0、false等
,而不是代码中设置的具体值,代码中设置的值是在初始化阶段
完成的。另外这里也不包含用final修饰的静态变量,由于final在编译的时候就会分配了。这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例对象会随着对象一块儿分配到Java堆中。
public class HelloApp { private static int a = 1; // 准备阶段为0,而不是1 public static void main(String[] args) { System.out.println(a); } }
解析(Resolve)
解析主要是解析字段、接口、方法。主要是将常量池中的符号引用替换为直接引用的过程。直接引用就是直接指向目标的指针、相对偏移量等。
<clinit>()
的过程<clinit>()
不一样于类的构造器(构造器是虚拟机视角下的<init>()
)<clinit>()
执行前,父类的<clinit>()
已经执行完毕<clinit>()
方法在多线程下被同步加锁须要注意,若是没有定义静态变量或静态代码块的话则没有<clinit>()
案例以下:
public class HelloApp { static { code = 20; } private static int code = 10; //第一步:在准备阶段初始化了code默认值为0。 //第二步:根据类执行顺序先执行静态代码块,赋值为20. //第三步:最后赋值为10,输出结果为10. public static void main(String[] args) { System.out.println(code); // 10 } }
经过字节码文件能够很清楚的看到结果:
0 bipush 20 2 putstatic #3 <com/jvm/HelloApp.code> 5 bipush 10 7 putstatic #3 <com/jvm/HelloApp.code> 10 return
先被赋值为20,而后改成10。
JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)
和 自定义类加载器(User-Defined ClassLoader)
从概念上讲,自定义类加载器通常指的是程序中由开发人员自定义的一类类加载器,可是Java虚拟机是将全部派生于抽象类ClassLoader的类加载器都划分为自定义类加载器
不管怎么划分,在程序中最多见的类加载器始终只有三个:
系统类加载器(System Class Loader) < 扩展类加载器(Extension Class Loader) < 引导类加载器(Bootstrap Class Loader)
它们之间的关系不是继承关系,而是level关系。
系统类加载器和扩展类加载器间接或直接继承ClassLoader。划线分为两大类。
public class HelloApp { public static void main(String[] args) { //获取系统类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 //获取其上层:扩展类加载器 ClassLoader extClassLoader = systemClassLoader.getParent(); System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@60e53b93 //获取其上层:获取不到引导类加载器 ClassLoader bootStrapLoader = extClassLoader.getParent(); System.out.println(bootStrapLoader);//null //咱们本身定义的类是由什么类加载器加载的:使用系统类加载器 ClassLoader classLoader = HelloApp.class.getClassLoader(); System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 //看看String是由什么类加载器加载的:使用引导类加载器 ClassLoader classLoaderString = String.class.getClassLoader(); System.out.println(classLoaderString);//null } }
引导类加载器(Bootstrap ClassLoader)
扩展类加载器(Extension ClassLoader)
若是用户建立的Jar放在此目录下,也会自动由扩展类加载器加载。
系统类加载器(System ClassLoader)
该类加载是程序中默认的类加载器
,通常来讲,Java应用的类都有由它来完成加载。为何须要用户自定义类加载器?
用户自定义类加载器实现步骤
关于ClassLoader
ClassLoader是一个抽象类,系统类加载器和扩展类加载器间接或直接继承ClassLoader。
经常使用方法以下:
方法名称 | 描述 |
---|---|
getParent() | 返回该类加载器的上一级类加载器 |
loadClass(String name) | 加载名称为name的类,返回结果为java.lang.Class的实例 |
findClass(String name) | 查找名称为name的类,返回结果为java.lang.Class的实例 |
findLoadedClass(String name) | 查找名称为name的已经被加载过的类,返回结果为java.lang.Class类的实例 |
defineClass(String name,byte[] b,int off,int len) | 把字节数组b中的内容转换为一个java类,返回结果为java.lang.Class类的实例 |
resolveClass(Class<?> c) | 链接指定的一个java类 |
Java虚拟机对class文件采用的是按需加载
的方式,也就是说须要使用该类的时候才会将它的class文件加载到内存生成class对象。
当某个类加载器须要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操做,若是上级的类加载器没有加载,本身才会去加载这个类。
工做原理
好比咱们如今在本身项目中建立一个包名java.lang下建立一个String类。
package java.lang; public class String { static { System.out.println("我是本身建立的String"); } }
public class HelloApp { public static void main(String[] args) { String s = new String(); } }
执行以后并不会输出咱们的语句,由于咱们的String类加载器一开始由系统类加载器一级一级往上委托,最终交给引导类加载器,引导类加载器一看是java.lang包下的,ok,我来执行,最终执行的并非咱们本身建立String类,保证了核心api没法被纂改,避免类的重复加载。
package java.lang; public class String { static { System.out.println("我是本身建立的String"); } public static void main(String[] args) { System.out.println("Hello World !!!"); } }
若是咱们想运行如上代码,咱们会获得以下错误:
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为: public static void main(String[] args) 不然 JavaFX 应用程序类必须扩展javafx.application.Application
由于咱们知道在核心api String类中是没有main方法的,因此咱们能够肯定加载的并非咱们本身建立的String类。
在JVM中表示两个Class对象是否为同一个类存在的必要条件:
顺便说一句,咱们包名若是为java.lang则会报错。