咱们平时说的栈是指的Java栈,native method stack 里面装的都是native方法java
- 方法区并非存放方法的区域,其是存放类的描述信息(模板)的地方
- Class loader只是负责class文件的加载,至关于快递员,这个“快递员”并非只有一家,Class loader有多种
- 加载以前是“class”,加载以后就变成了“Class”,这是安装java.lang.Class模板生成了一个实例。“Class”就装载在方法区,模板实例化以后就获得n个相同的对象
- JVM并非经过检查文件后缀是否是
.class
来判断是否须要加载的,而是经过文件开头的特定文件标志
注意:加载阶段失败会直接抛出异常api
把.class文件读入到java虚拟机中数组
动态编译:jsp-->java-->class安全
将字节流所表明的静态存储结构转换为方法区的运行时数据结构数据结构
在java堆中生成一个表明这个类的java.lang.Class对象,做为方法区这些数据的访问入口架构
确保class文件的字节流中包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身安全。dom
验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。jvm
虚拟机常量池内的符号引用替换为直接引用 ,类名、字段名、方法名--->具体内存地址或偏移量jsp
类变量被赋值、实例变量被初始化ide
每一个类/接口被Java程序首次主动使用的时候才会被java虚拟机初始化
从上到下初始化、
初始化一个类时,要求它的父类都已经被初始化了(除接口)
当初始化一个类的时候并不会先初始化它实现的接口
当初始化一个接口的时候,并不会初始化它的父接口
一个父接口并不会由于它的子接口或实现类的初始化而初始化,只有当首次使用其特定的静态变量时(即运行时常量,如接口中引用类型的变量)时才会初始化
[L+自定义类全类名
(一维)这种形式public class ClassLoaderTest { public static void main(String[] args) { //单独测试下列语句 //1. System.out.println(Child.str1); /*输出 * Parent static block * hello I'm Parent */ //2. System.out.println(Child.str2); /*输出 * Parent static block * Child static block * hello I'm Child */ //3. System.out.println(Parent.str3); /*输出 * hello I'm Parent2 * */ //4. System.out.println(Parent.str4); /*输出 * Parent static block * 78f59c0d-b91c-4e32-8109-dec5cb23aa13 * */ //5. Parent[] parents1=new Parent[1]; System.out.println(parents1.getClass()); Parent[][] parents2=new Parent[2][2]; System.out.println(parents2.getClass()); /*输出 * class [Lcom.lx.Parent; * class [[Lcom.lx.Parent; * */ //6. System.out.println(Singleton1.count1); System.out.println(Singleton1.count2); System.out.println(Singleton2.count1); System.out.println(Singleton2.count2); /*输出 * 1,1,1,0 * */ } } class Parent{ public static String str1 = "hello I'm Parent"; public static final String str3 = "hello I'm Parent2"; public static final String str4 = UUID.randomUUID().toString(); static { System.out.println("Parent static block"); } } class Child extends Parent{ public static String str2 = "hello I'm Child"; static { System.out.println("Child static block"); } } class Singleton1 { public static int count1; public static int count2=0; public static Singleton1 singleton1=new Singleton1(); public Singleton1() { count1++; count2++; } public Singleton1 getInstance(){ return singleton1 ; } } class Singleton2 { public static int count1; public static Singleton2 singleton2=new Singleton2(); public Singleton2() { count1++; count2++; } public static int count2=0; public Singleton2 getInstance(){ return singleton2 ; } }
类加载状况
状况1:
类的加载并不是必定要该类被主动使用化
状况2:同上
状况3:
自定义的类只加载了启动类(调用常量的方法所在的类)
状况4:加载启动类以及Parent类
反编译结果
状况1:
状况2:相似1
状况3:没有引用到Parent类(定义常量的类)
状况4:相似1
接口中定义的变量都是常量
常量又分为编译期常量和运行期常量,编译期常量的值在编译期间就能够肯定,直接存储在了调用类的常量池中,因此访问接口中的编译期常量并不会致使接口的初始化,只有访问接口中的运行期常量才会引发接口的初始化。
父接口并不会由于子接口或是实现类的初始化而初始化,当访问到了其特定的静态变量时(即运行时常量,如接口中引用类型的变量)才会初始化
public class ClassLoaderTest2 { public static void main(String[] args) { System.out.println(new demo2().a); System.out.println("====="); System.out.println(son1.a); new demo1().show(); System.out.println(demo1.str); System.out.println(son1.b); System.out.println(demo1.s);//System.out.println(son1.s); /*输出 * father2 singleton * 1 * ===== * 1 * show method * string * father1 singleton * com.lx.father1$1@1b6d3586 * */ } } interface father1{ int a=1; void show(); String str="string"; Singleton1 s=new Singleton1(){ { System.out.println("father1 singleton"); } }; } interface son1 extends father1 { int b=0; Singleton1 s1=new Singleton1(){ { System.out.println("son1 singleton"); } }; } class demo1 implements father1{ @Override public void show() { System.out.println("show method"); } } class father2{ int a=1; void show(){} String str="string"; Singleton1 s=new Singleton1(){ { System.out.println("father2 singleton"); } }; } class demo2 extends father2{ }
第3行:子类初始化前必须初始化父类
第5-8行:访问到编译时常量(已经存入了调用方法类的常量池中),不会致使初始化
第9行: 访问了运行时常量,须要初始化定义该运行时常量的类
1、java虚拟机自带的类加载器
启动类加载器(Bootstrap) ,C++所写,不是ClassLoader子类
扩展类加载器(Extension) ,Java所写
应用程序类加载器(AppClassLoader)。
2、用户自定义的类加载器
import com.gmail.fxding2019.T; public class Test{ //Test:查看类加载器 public static void main(String[] args) { Object object = new Object(); //查看是那个“ClassLoader”(快递员把Object加载进来的) System.out.println(object.getClass().getClassLoader()); //查看Object的加载器的上一层 // error Exception in thread "main" java.lang.NullPointerException(已是祖先了) //System.out.println(object.getClass().getClassLoader().getParent()); System.out.println(); Test t = new Test(); System.out.println(t.getClass().getClassLoader().getParent().getParent()); System.out.println(t.getClass().getClassLoader().getParent()); System.out.println(t.getClass().getClassLoader()); } } /* *output: * null * * null * sun.misc.Launcher$ExtClassLoader@4554617c * sun.misc.Launcher$AppClassLoader@18b4aac2 * */
- 若是是JDK自带的类(Object、String、ArrayList等),其使用的加载器是Bootstrap加载器;若是本身写的类,使用的是AppClassLoader加载器;Extension加载器是负责将把java更新的程序包的类加载进行
- 输出中,sun.misc.Launcher是JVM相关调用的入口程序
- Java加载器个数为3+1。前三个是系统自带的,用户能够定制类的加载方式,经过继承Java. lang. ClassLoader
Java虚拟机采用按需加载的方式,当须要使用该类是才会去讲class文件加载到内存生成class对象,加载类是采用的是双亲委派机制
自底向上检查类是否已经被加载
自顶向下尝试加载类
原理图:
另一种机制:
双亲委派优点:
- 避免类的重复加载。
- 保护程序安全、防止核心api被恶意篡改(以下例子)
用户自定义的类加载器不可能加载到一个有父加载器加载的可靠类,从而防止不可靠恶意代码代替父加载器加载的可靠的代码。例如:Object类老是有跟类加载器加载,其余用户自定义的类加载器都不可能加载含有恶意代码的Object类
//测试加载器的加载顺序 package java.lang; public class String { public static void main(String[] args) { System.out.println("hello world!"); } } /* * output: * 错误: 在类 java.lang.String 中找不到 main 方法 * */
解释:
交给启动类加载器以后(java.lang.String/由java开头的包名)归它管,因此它首先加载这个类(若是核心api内没有改类也会报错),轮不到让系统类加载器去加载该类,即没法加载到本身所写的String类,核心api中的String类没有main方法,因此会报错说找不到main方法
类的实例化
判断为同一个类的必要条件
使用类加载器的缘由
自定义类加载器
获取类加载器方法:
沙箱安全机制
命名空间
loadClass方法
经过调用ClassLoader类的loadClass方法加载一个类,并非对一个类的主动使用,不会致使初始化。
类的卸载