一、基础架构

概览

我们平时说的栈是指的Java栈,native method stack 里面装的都是native方法

细节架构图


二、类加载器

1、类的加载

  • 方法区并不是存放方法的区域,其是存放类的描述信息(模板)的地方
  • Class loader只是负责class文件的加载,相当于快递员,这个“快递员”并不是只有一家,Class loader有多种
  • 加载之前是“class”,加载之后就变成了“Class”,这是安装java.lang.Class模板生成了一个实例。“Class”就装载在方法区,模板实例化之后就得到n个相同的对象
  • JVM并不是通过检查文件后缀是不是.class来判断是否需要加载的,而是通过文件开头的特定文件标志

2、类的加载过程

注意:加载阶段失败会直接抛出异常

2.1、加载

​ 把.class文件读入到java虚拟机中

  • 通过“类全名”来获取定义此类的二进制字节流

​ 动态编译:jsp-->java-->class

  • 将字节流所代表的静态存储结构转换为方法区的运行时数据结构

  • 在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口

2.2、链接

1. 验证
  • 确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。

  • 验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。

2. 准备
  • 类变量(静态变量)分配内存并设置类变量默认值-->0/false/null(不包含final修饰的static,final修饰的变量会显示初始化
  • 在初始化之前,若使用了类变量,用到的是默认值,并非代码中赋值的值
  • 不会实例变量分配初始化、类变量分配在方法区中,实例变量会随对象分配到java堆中
3. 解析

​ 虚拟机常量池内的符号引用替换为直接引用 ,类名、字段名、方法名--->具体内存地址或偏移量

2.3、初始化

1. 主动/被动使用

2. 初始化注意点
  • 类变量被赋值、实例变量被初始化

  • 每个类/接口被Java程序首次主动使用的时候才会被java虚拟机初始化

  • 从上到下初始化

  • 初始化一个类时,要求它的父类都已经被初始化了(除接口)

    • 当初始化一个类的时候并不会先初始化它实现的接口

    • 当初始化一个接口的时候,并不会初始化它的父接口

      一个父接口并不会因为它的子接口或实现类的初始化而初始化,只有当首次使用其特定的静态变量时(即运行时常量,如接口中引用类型的变量)时才会初始化

3. 深入理解举例1
  • 对于静态字段来说,只有直接定义了该字段的类才会被初始化
  • 每个类在初始化前,必须先初始化其父类(除接口)
  • 追踪类的加载情况:-XX:+TraceClassLoading(+表示开启,-表示关闭)
  • 对于常量(这里指编译器确定的常量)来说,常量值在编译阶段会存入到调用它的方法所在的类的常量池中,本质上调用类没有直接引用到定义常量的类
  • 对于引用类型数组来说,其类型是由JVM在运行期间动态生成的,表示为[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 ;
}
}
4. 结果分析
  1. Child属于被动使用,Parent是主动使用,所以只会初始化Parent
  2. Child属于主动使用,所以会初始化Child,由于初始化的类具有父类所以先初始化父类
  3. Parent并没有被使用到,str3的值在编译期间就被存入CLassLoaderTest这个调用它的方法所在的类的常量池中,与Parent无关
  4. str4不是编译期间就能确定的常量,就不会放到调用方法类的常量池中,在运行时主动使用Parent类进而需要初始化该类
  5. 没有对Parent类初始化,引用数组类型并非Parent类,而是jvm动态生成的class [Lcom.lx.Parent
  6. 首先访问Singleton的静态方法--》Singleton是主动使用--》先初始化
    1. 第一种:准备阶段给count1,2分配空间默认值已经为0了,此时给类变量singleton初始化,调用构造方法,分别加一
    2. 第二种:同上,但是在给singleton初始化时,count2并未初始化,自增只是暂时的,随后就要对它初始化,所以在count2初始化前对他进行的操作时无效的。

类加载情况

情况1:

  • 加载object....类
  • 加载启动类
  • 加载父类
  • 加载子类

类的加载并非一定要该类被主动使用化

情况2:同上

情况3:

​ 自定义的类只加载了启动类(调用常量的方法所在的类)

情况4:加载启动类以及Parent类

反编译结果

情况1:

情况2:类似1

情况3:没有引用到Parent类(定义常量的类)

情况4:类似1

5. 深入理解举例2

接口中定义的变量都是常量

常量又分为编译期常量和运行期常量,编译期常量的值在编译期间就可以确定,直接存储在了调用类的常量池中,所以访问接口中的编译期常量并不会导致接口的初始化,只有访问接口中的运行期常量才会引起接口的初始化。

父接口并不会因为子接口或是实现类的初始化而初始化,当访问到了其特定的静态变量时(即运行时常量,如接口中引用类型的变量)才会初始化

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{ }
6. 结果分析

​ 第3行:子类初始化前必须初始化父类

​ 第5-8行:访问到编译时常量(已经存入了调用方法类的常量池中),不会导致初始化

​ 第9行: 访问了运行时常量,需要初始化定义该运行时常量的类

3、类加载器分类

一、java虚拟机自带的类加载器

  1. 启动类加载器(Bootstrap) ,C++所写,不是ClassLoader子类

  2. 扩展类加载器(Extension) ,Java所写

  3. 应用程序类加载器(AppClassLoader)。

    • 自定义类一般为系统(应用)类加载器加载

二、用户自定义的类加载器

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

4、双亲委派机制

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方法

5、补充:

类的实例化

  • 为新的对象分配内存
  • 为实例变量赋默认值
  • 为实例变量赋值(自己定义的)
  • 为其生成/方法或者说构造方法

判断为同一个类的必要条件

使用类加载器的原因

自定义类加载器

获取类加载器方法:

沙箱安全机制

命名空间

loadClass方法

​ 通过调用ClassLoader类的loadClass方法加载一个类,并不是对一个类的主动使用,不会导致初始化。

类的卸载


JVM入门--类加载器的更多相关文章

  1. JVM自定义类加载器加载指定classPath下的所有class及jar

    一.JVM中的类加载器类型 从Java虚拟机的角度讲,只有两种不同的类加载器:启动类加载器和其他类加载器. 1.启动类加载器(Boostrap ClassLoader):这个是由c++实现的,主要负责 ...

  2. 【深入理解JVM】类加载器与双亲委派模型 (转)

    出处: [深入理解JVM]类加载器与双亲委派模型 加载类的开放性 类加载器(ClassLoader)是Java语言的一项创新,也是Java流行的一个重要原因.在类加载的第一阶段“加载”过程中,需要通过 ...

  3. 1.1 jvm核心类加载器--jdk源码剖析

    目录 前提: 运行环境 1. 类加载的过程 1.1 类加载器初始化的过程 1.2 类加载的过程 1.3 类的懒加载 2. jvm核心类加载器 3. 双亲委派机制 4. 自定义类加载器 5. tomca ...

  4. 【JVM】JVM之类加载器

    一.前言 首先,小小测试,看是否已经掌握了JVM类加载的过程 1.1.测试一 class Singleton { private static Singleton sin = new Singleto ...

  5. JVM之类加载器下篇

    除了自定义的类加载之外,jvm存在三种类加载器,并以一种父委托的加载机制进行加载. --启动类加载器,又称根加载器,是一个native的方法,使用c++实现.在java中我们用null标识,用于加载j ...

  6. 【深入理解JVM】类加载器与双亲委派模型

    原文链接:http://blog.csdn.net/u011080472/article/details/51332866,http://www.cnblogs.com/lanxuezaipiao/p ...

  7. JVM学习一:JVM之类加载器概况

    18年转眼就3月份都快结束了,也就是说一个季度就结束了:而我也因为年前笔记本坏了,今天刚修好了,那么也应该继续学习和博客之旅了.今年的博客之旅,从JVM开始学起,下面我们就言归正传,进入正题. 一.J ...

  8. (转)JVM——自定义类加载器

    背景:为什么要自定义,如何自定义,实现过程 转载:http://blog.csdn.net/SEU_Calvin/article/details/52315125 0. 为什么需要自定义类加载器 网上 ...

  9. JVM虚拟机-类加载器子系统

    转自博客:http://www.cnblogs.com/muffe/p/3541189.html   还有一些自己补充的知识点 一.类加载器基本概念 顾名思义,类加载器(class loader)用来 ...

随机推荐

  1. PHP函数:array_key_exists

    array_key_exists()  - 检查数组里是否有指定的键名或索引. 注意:array_key_exists() 仅仅搜索第一维的键. 多维数组里嵌套的键不会被搜索到. 说明: rray_k ...

  2. [护网杯2018] easy_laravel

    前言 题目环境 buuoj 上的复现,和原版的题目不是完全一样.原题使用的是 nginx + mysql 而 buuoj 上的是 apache + sqlite composer 这是在 PHP5.3 ...

  3. 人体和电脑的关系——鸟哥的LINUX私房菜基础学习篇读书笔记

    CUP=脑袋: 每个人会做的事情都不一样(指令集的差异),但主要都是通过脑袋来判断与控制身体各部分的行动 内存=脑袋中存放正在思考的数据区块: 在实际活动过程中,我们的脑袋需要有外界刺激的数据(例如光 ...

  4. [源码分析] 带你梳理 Flink SQL / Table API内部执行流程

    [源码分析] 带你梳理 Flink SQL / Table API内部执行流程 目录 [源码分析] 带你梳理 Flink SQL / Table API内部执行流程 0x00 摘要 0x01 Apac ...

  5. 取代 Python 多进程!伯克利开源分布式框架 Ray

    Ray 由伯克利开源,是一个用于并行计算和分布式 Python 开发的开源项目.本文将介绍如何使用 Ray 轻松构建可从笔记本电脑扩展到大型集群的应用程序. 并行和分布式计算是现代应用程序的主要内容. ...

  6. 【MyBatis深入剖析】应用分析与最佳实践(下)

    MyBatis编程式开发 MyBatis编程式开发步骤 MyBatis和MySQL Jar包依赖 全局配置文件mybatis-config.xml 映射器Mapper.xml Mapper接口 编程式 ...

  7. Java 多线程 -- volatile 山寨版的synchronized

    在 多线程中,每个线程会把数据从主内存中拷贝到自己的工作内存中,当线程完成计算后,再把工作内存的数据更新到主内存中,或者当主内存主数据有更新是,线程会去主内存取最新数据.但是,当线程特别忙时,就不会去 ...

  8. JS在线代码编辑器多种方案monaco-editor,vue-monaco-editor

    前言 JavaScript在线代码编辑器. 需要代码提示,关键字高亮,能够格式化代码.(不需要在线运行) 简简单单的需求. 方案一: Monaco-editor 简介:微软的开源项目,开源中国上面的在 ...

  9. python学习笔记(六)---文件操作与异常处理机制

    文件读取 读取整个文件 要读取文件,需要一个包含几行文本的文件.下面首先来创建一个文件,它包含精确到小数点后30位的圆周率值,且在小数点后每10位处都换行: pi_digits.txt 3.14159 ...

  10. Spring Boot中的测试

    文章目录 简介 添加maven依赖 Repository测试 Service测试 测试Controller @SpringBootTest的集成测试 Spring Boot中的测试 简介 本篇文章我们 ...