02--JVM工作过程

1.类加载机制

Java源文件 --> 编译器 --> 字节码文件 --> JVM --> 机器码

(1)类加载的时机

主动初始化的6种方式:
	创建实例对象:new对象的时候,会对类初始化,前提这个类没有被初始化
	调用类的静态属性或为静态属性赋值
	调用类的静态方法
	通过class文件反射创建对象
	初始化一个类的子类:使用子类的时候先初始化父类
	Java虚拟机启动时被标记为启动类的类:比如main方法所在的类
	
不会进行初始化的情况:
	在同一类加载器下面只能初始化一次,如果已经初始化了就不必再初始化。
	在编译的时候能确定下来的静态常量(编译常量),不会对类进行初始化。比如final修饰的静态变量。

(2)类加载过程

负责将字节码文件(.class)载入到内存。

1.类加载器
	启动类加载器(Bootstrap ClassLoader):主要加载JAVA_HOME/jre/lib 里的
jar 包,该目录下的所有jar包都是运行JVM 时所必需的jar包。不是ClassLoader的子类。
	扩展类加载器(ExtClassLoader):主要加载Java 核心扩展类,即JAVA_HOME/jre/ext 目录下的jar 文件。
	应用类加载器(AppClassLoader):主要加载的是开发者在应用程序中编写的类,即CLASSPATH 路径下所有的jar 文件。

2.双亲委派模型
	(1)当前类加载器从自己已经加载的类中查询是否此类已经加载,如果已经加载则返回原来已经加载的类。
	(2)如果没有找到,就去委托父类加载器去加载。父类加载器也会采用同样的策略,查看自己已经加载过的类中是否包含这个类,有就返回,没有就委托其父类去加载,直到委托到启动类	加载器为止。因为如果父类加载器为空了,就代表使用启动类加载器作为父加载器去加载该类。(也就是看到的String 类加载器为null)
	(3)如果启动类加载器加载失败,就会使用扩展类加载器来尝试加载,继续失败则会使用AppClassLoader 来加载,继续失败就会抛出一个异常ClassNotFoundException。
	
使用双亲委派模型的好处:
	(1)安全性,避免用户自己编写的类动态替换Java 的一些核心类。如果不采用双亲委派模型的加载方式进行类的加载工作,那我们就可以随时使用自定义的类来动态替代Java 核心API 中定义的类。例如:如果黑客将“病毒代码”植入到自定义的String 类当中,随后类加载器将自定义的String 类加载到JVM 上,那么此时就会对JVM 产生意想不到“病毒攻击”。而双亲委派的这种加载方式就可以避免这种情况,因为String 类已经在启动时就被引导类加载器进行了加载。
	(2)避免类的重复加载,因为JVM 判定两个类是否是同一个类,不仅仅根据类名是否相同进行判定,还需要判断加载该类的类加载器是否是同一个类加载器,相同的class 文件被不同的类加载器加载得到的结果就是两个不同的类。

3.自定义类加载器
	如果用户想加载一个特定目录下的类时,就要用到用户自定义的类加载器。除了启动类加载器之外,所有的类加载器都是ClassLoader 的子类。
	如果要编写一个自定义类加载器MyClassLoader,那么很自然地MyClassLoader 就要继承自ClassLoader 类。

4.类加载的详细过程
加载器加载到JVM 中,接下来其实又分了好几个步骤:
	加载:查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象。
	连接:连接又包含三块内容:验证、准备、初始化
		1)验证:文件格式、元数据、字节码、符号引用验证;
		2)准备:为类的静态变量分配内存,并将其初始化为默认值;
		3)解析:把类中的符号引用转换为直接引用
	初始化:为类的静态变量赋予正确的初始值。

2.Java内存模型

功能、是否线程共享、生命周期、抛出异常

(1)程序计数器

程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。

主要作用:
	字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制。如:顺序执行、选择、循环、异常处理。
	在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪了。

注意:程序计数器是唯一一个不会出现OutMemoryError的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。

(2)Java虚拟机栈

与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是Java方法执行的内存模型。

Java内存可以粗糙的区分为堆内存(Heap)和栈内存(Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。(实际上,Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。)
	局部变量表中包括:八大原始类型、对象引用、returnAddress

Java虚拟机栈会出现两种异常:StackOverFlowError和OutOfMemoryError。
	StackOverFlowError:若Java虚拟机栈的内存大小不允许动态扩展,当线程请求栈的深度超过当前Java虚拟机栈的最大深度时,就抛出StackOverFlowError异常。
	OutOfMemoryError:若Java虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。

Java虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机,而且随着线程的创建而创建,随着线程的结束而死亡。

(3)本地方法栈

和虚拟机栈所发挥的作用非常相似。
区别是:虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Nitive方法服务。
也会出现StackOverFlowError和OutOfMemoryError两种异常。

(4)堆

Java虚拟机所管理的内存中最大的一块,Java堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
Java堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap)。

(5)方法区

方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
发布了11 篇原创文章 · 获赞 0 · 访问量 132

猜你喜欢

转载自blog.csdn.net/baidu_41806513/article/details/105052754