深入理解JVM之类加载

---
title: 【学习】深入理解JVM之类加载.md
date: 2019-10-20 22:20:06
tags: JVM 类加载
---

Java类的加载,连接,初始化都是在程序运行期间执行的

## Java 虚拟机与程序的生命周期

1. 执行 System.exit()方法
2. 程序正常结束
3. 遇到异常或错误终止
4. 由于操作系统或程序虚拟机进程错误

以上的情况都可以结束生命周期

## Java 类加载的方式

1. 本地系统直接加载
2. 通过网络下载.class 文件
3. 通过zip,jar 等归档文件加载
4. 从专有数据库中加载.class 文件
5. 通过java 文件动态编译源文件得到.class 文件

## 类加载器有哪些

- 系统自带的类加载器
- 根类加载器(Bootstrap ClassLoader)
- 扩展类加载器(Extension ClassLoader)
- 系统类加载器(System ClassLoader)
- 用户自定义类加载器
- 通过ClassLoader来实现自定义的类加载器

使用java.lang.ClassLoader的子类,通过ClassLoader来实现自定义的类加载器,这个过程中可以定义类的**加载方式**,**加载时机**以及加载过程中做一些事情。

## 类加载过程

? **类的加载指的是将类的.class 文件中的二进制数据读入到内存中.将其放在运行时数据区的方法区内,然后在内存中创建一个 java.lang.Class 对象(虚拟机规范并未说明Class对象在哪里,HotSpot 虚拟机将其放在方法区)用来封装类在方法区内的数据结构**

- 加载

查找并加载类的二进制数据

- 连接

- 验证

确保被加载的类的**正确性**

- 准备

为类的**静态变量**分配内存, 并将其初始化为**默认值**

- 解析

把类中的**符号引用换做**是**直接引用**

- 初始化

为类的静态变量赋予**正确的值**

## Java 程序对类的使用方式

所有的 java 虚拟机实现必须在每个类或接口被 Java 程序**首次主动使用**才初始化

-XX:+TraceClassLoading 可以查看类加载

- 主动使用会初始化(7 个)

1. 创建类实例
2. 访问某个类或接口的静态变量,或者对该类的静态变量赋值
3. 调用类的静态方法
4. 反射(Class.forName("com.test.Test"))
5. 初始化一个类的子类
6. java 虚拟机启动时,被表明为启动类的类
7. JDK1.7提供的实例解析结果REF_getStatic,REF_putStatic,REF_invokeStatic句柄对应的类没有初始化则初始化

- 被动使用初始化

> 对于静态字段来说,只有**直接定义了该字段的类**才会被初始化

~~~java
public class Mytest1 {
public static void main(String[] args) {
System.out.println(Mychirld1.str);
}
}

/**
* 对于静态字段来说,只有直接定义了该字段的类才会被初始化
*/
class Myparent1 {
public static String str = "hello world !";

static {
System.out.println("current in Myparent1");
}
}

class Mychirld1 extends Myparent1{
public static String str2 = "hello world2 !";
static {
System.out.println("current in Mychirld1");
}
}

>>>>>>
current in Myparent1
hello world !
~~~

main 方法里使用子类去调用了父类的静态变量 因只有直接定义了该字段的类才会被初始化,所以先初始化打印父类的静态代码块内容,且子类不初始化

> 当一个类初始化的时候,要求其父类全部初始化完毕

这个是主动使用,就会初始化

~~~java
public class Mytest1 {
public static void main(String[] args) {
System.out.println(Mychirld1.str2);
}
}

/**
* 对于静态字段来说,只有直接定义了该字段的类才会被初始化
*/
class Myparent1 {
public static String str = "hello world !";

static {
System.out.println("current in Myparent1");
}
}

class Mychirld1 extends Myparent1{
public static String str2 = "hello world2 !";
static {
System.out.println("current in Mychirld1");
}
}

>>>>>>
current in Myparent1
current in Mychirld1
hello world2 !
~~~

main 方法中调用了子类的静态变量,主动初始化子类,根据类的加载顺序会先初始化父类,所以先打印父类的静态代码块中的东西,在打印子类的.

> 被 final 修饰的变量

当一个main 方法引用了一个类的被 final 修饰的静态变量会主动触发其初始化么?

~~~java
public class Mytest2 {
public static void main(String[] args) {
System.out.println(Myparent2.str);
}
}

class Myparent2 {
public static final String str = "hello world !";

static {
System.out.println("current in Myparent1");
}
}

>>>>>
hello world !
~~~

答案是不会的,

因为被 final 修饰作为一个常量,会在编译阶段,就会被放置在调用该常量的方法所在的类的常量池中.本质上,调用该常量的类和常量定义的类已经没有直接引用的关系了,我们将编译时期Myparent2.class 删除,运行 main 方法也会打印hello world !

>创建数组实例

~~~java
public class Mytest3 {
public static void main(String[] args) {
Myparent3[] myparent3s = new Myparent3[1];
}
}

class Myparent3 {
static {
System.out.println("current in parent3");
}

}
~~~

这种情况什么输出都没有.因为对于数组实例来说,其类型是由 JVM 在运行期动态生成

> 接口初始化

接口中的字段默认都是public static final

***当一个接口初始化时,并不要求其父接口都完成了初始化只有在真正使用到父接口的时候(如引用到接口中所定义的常量时),才会初始化(但子接口初始化,父接口一定会被加载)***

~~~java
public class Mytest5 {
public static void main(String[] args) {
System.out.println(Mychirld5.b);
}
}

interface Myparent5 {
public static final int a = 7;

}

interface Mychirld5 extends Myparent5 {
public static final int b = new Random().nextInt(2);
}

>>>>
可通过 -XX:+TraceClassLoading查看父接口是否被初始化
~~~

> 助记符

```
/**
* 助记符
*
* idc 表示将 int,float 或是 String 类型的常量值从常量池中推送至栈顶
* bipush 表示将单字节(-128~127)的常量值推送至栈顶
* sipush 表示将一个短整型常量值(-32768~32767)推送至栈顶
* iconst_1 表示将 int 类型 1 推送至栈顶(iconst_1 - iconst_5)
*/
```

## 总结

类加载的整个过程

开始 ---> 有类加载器加载类 ---> 连接 ----> 初始化 ---> 使用类 ---> 卸载

? |---->验证

? | ----> 准备

? | ----> 解析

> 加载

就是把二进制类型的 java 类型读入 java 虚拟机红

> 连接

验证:

确保被加载的类的**正确性

准备:

1.为变量分配内存,设置默认值 2.在未达到初始化,类变量都没有初始化为真正的初始值

解析:

在类型的常量池中,寻找类,接口,字段和方法的符号引用,把这些引用替换为直接引用的过程

> 初始化

为 变量赋初始值

> 类实例化

1.为新的对象分配内存 2.为实例变量赋默认值3.为实例变量赋正确的初始值

原文地址:https://www.cnblogs.com/ankuo/p/11746360.html

时间: 2024-11-05 19:01:47

深入理解JVM之类加载的相关文章

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

原文链接:http://blog.csdn.net/u011080472/article/details/51332866,http://www.cnblogs.com/lanxuezaipiao/p/4138511.html 加载类的开放性 类加载器(ClassLoader)是Java语言的一项创新,也是Java流行的一个重要原因.在类加载的第一阶段"加载"过程中,需要通过一个类的全限定名来获取定义此类的二进制字节流,完成这个动作的代码块就是类加载器.这一动作是放在Java虚拟机外部

深入理解JVM(九)——类加载的过程

通过之前的介绍可知,类加载过程共有5个步骤,分别是:加载.验证.准备.解析.初始化.其中,验证.准备.解析称为连接.下面详细介绍这5个过程JVM所做的工作. 加载 注意:"加载"是"类加载"过程的第一步,千万不要混淆. 1. 加载的过程 在加载过程中,JVM主要做3件事情: 通过一个类的全限定名来获取这个类的二进制字节流,即class文件: 在程序运行过程中,当要访问一个类时,若发现这个类尚未被加载,并满足类初始化时机的条件时,就根据要被初始化的这个类的全限定名找到

【深入理解JVM】:类加载机制

概述 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 与那些在编译时需要进行链接工作的语言不同,在Java语言里,类型的加载.连接和初始化过程都是在程序运行期间完成的,例如import java.util.*下面包含很多类,但是,在程序运行的时候,虚拟机只会加载哪些我们程序需要的类.这种策略虽然会令类加载时稍微增加一些性能开销,但是会为Java应用程序提供高度的灵活性. 类加载的时机 类从

深入理解JVM虚拟机6:深入理解JVM类加载机制

深入理解JVM类加载机制 简述:虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 下面我们具体来看类加载的过程: 类的生命周期 类从被加载到内存中开始,到卸载出内存,经历了加载.连接.初始化.使用四个阶段,其中连接又包含了验证.准备.解析三个步骤.这些步骤总体上是按照图中顺序进行的,但是Java语言本身支持运行时绑定,所以解析阶段也可以是在初始化之后进行的.以上顺序都只是说开始的顺序,实际过

深入理解JVM(8)——类加载的时机

一.类的生命周期 一个类从加载进内存到卸载出内存一共要经历7个阶段:加载—>验证—>准备-->解析—>初始化—>使用—>卸载. 类加载包括五部分:加载—>验证—>准备-->解析—>初始化 在类加载的过程中,以下三个过程成为连接:验证—>准备-->解析 因此,JVM的类加载过程可以分为三个部分:加载—>连接—>初始化 c/c++语言在程序运行之前需要完成预处理.编译.汇编.连接,但是在Java语言中类加载(加载.连接.初始化

深入理解Java:类加载机制及反射

一.Java类加载机制 1.概述 Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能. 虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 2.工作机制 类装载器就是寻找类的字节码文件,并构造出类在JVM内部表示

【转】[译]深入理解JVM

http://www.cnblogs.com/enjiex/p/5079338.html 深入理解JVM 原文链接:http://www.cubrid.org/blog/dev-platform/understanding-jvm-internals 每个使用Java的开发者都知道Java字节码是在JRE中运行(JRE: Java 运行时环境).JVM则是JRE中的核心组成部分,承担分析和执行Java字节码的工作,而Java程序员通常并不需要深入了解JVM运行情况就可以开发出大型应用和类库.尽管

深入理解JVM之JVM内存区域与内存分配

深入理解JVM之JVM内存区域与内存分配 在学习jvm的内存分配的时候,看到的这篇博客,该博客对jvm的内存分配总结的很好,同时也利用jvm的内存模型解释了java程序中有关参数传递的问题. 博客出处: http://www.cnblogs.com/hellocsl/p/3969768.html?utm_source=tuicool&utm_medium=referral 看了此博客后,发现应该去深入学习下jvm的内存模型,就是去认真学习下<深入理解Java虚拟机>,其内容可能会<

【转】理解JVM内存区域

引言 对于C++程序员,内存分配与回收的处理一直是令人头疼的问题.Java由于自身的自动内存管理机制,使得管理内存变得非常轻松,不容易出现内存泄漏,溢出的问题. 不容易不代表不会出现问题,一旦内存泄漏或溢出的情况发生,调试起来会变得非常困难.这就要求我们对虚拟机的内存区域有深入的理解.最终能够判断内存方面的异常发生时,具体在JVM中的位置. 内存区域 JVM运行时,首先需要类加载器(ClassLoader) 加载所需类的字节码,加载完毕交由执行引擎执行,执行过程中需要一段空间来存储数据(类比CP