Java类的生命周期,能说出来算你牛!

Wesley13
• 阅读 655

Java类的生命周期,能说出来算你牛!

Java类的生命周期,能说出来算你牛!

**类生命周期
**

  • 加载(Loading)

  • 四种类加载器:

  • JAVA_HOME 目录里面的内容

  • 双亲委任

  • 连接(Linking)

  • 验证阶段

  • 准备阶段

  • 解析阶段

  • 初始化

  • 类初始化的七种触发情况:

  • 卸载

Java类的生命周期,能说出来算你牛!

JVM

==========

每本Java入门书籍在介绍Java这门语言的时候都会提到Java跨平台,“一次解释,到处运行的特点“,功臣就是jvm(Java Virtual Machine,Java虚拟机)。

但是,如果将jvm只与Java语言绑定在一起,那么理解就过于狭隘了,Java虚拟机发展到现在已经脱离了Java语言,形成了一套相对独立,高性能的执行方案。

Java类的生命周期,能说出来算你牛!

image

除了以上提到的几种语言之外,scala,热门的kotlin都可以运行在jvm上面。

Java类的生命周期,能说出来算你牛!

jvm内存结构规范

先简单看一下 JVM 内存结构,之后会详细讲解这一块的具体存储。

Java类的生命周期,能说出来算你牛!

类生命周期

============

类从被加载到虚拟内存中开始,到卸载内存为止,它的整个生命周期包括:

Java类的生命周期,能说出来算你牛!

类从加载到卸载整个生命周期

小提示:

  1. 加载阶段和连接阶段有时候是交叉进行的,不需要等到完全加载结束。

  2. 解析阶段有时候可以再初始化之后再做。Jvm仅仅规定了:如果某些字节码使用了符号引用,那么在执行这些字节码之前,需要完成对这些符号引用的解析。

  3. 但是这些过程总的开始时间和完成时间都是上图固定顺序。

  4. 这里的“加载阶段”和我们常说的“类加载”是两回事,“类加载”指的是虚线框中三部分加起来。

Java类的生命周期,能说出来算你牛!

加载(Loading)


加载,是指查找字节流,并且据此创建类的过程。是类加载过程的一个阶段。虚拟机需要在这个过程完成三件事情:

  • 通过一个类的全限定名来获取此类的二进制字节流;

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

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

Java类的生命周期,能说出来算你牛!

类加载

从虚拟机的角度来说,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),该类加载器使用C++语言实现,属于虚拟机自身的一部分。另外一种就是所有其它的类加载器,这些类加载器是由Java语言实现,独立于JVM外部,并且全部继承自抽象类java.lang.ClassLoader。

四种类加载器:

  1. 启动(Bootstrap)类加载器

启动类加载器负责加载最为基础、最为重要的类。负责将 JAVA_HOME/lib 下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

注:启动类加载器是由 C++ 实现的,没有对应的 Java 对象,因此在 Java 中只能用 null 来指代。除了启动类加载器之外,其他的类加载器都是 java.lang.ClassLoader 的子类,因此有对应的 Java 对象。这些类加载器需要先由另一个类加载器,比如说启动类加载器,加载至 Java 虚拟机中,方能执行类加载。

  1. 标准扩展(Extension)类加载器

它负责加载相对次要、但又通用的类,负责将 JAVA_HOME/jre/lib/ext 或者由系统变量 java.ext.dirs指定位置中的类库加载到内存中。

  1. 应用程序(Application)类加载器

它负责将系统类路径(CLASSPATH) 中指定的类库加载到内存中。由于这个类加载器是ClassLoader中的 getSystemClassLoader()方法的返回值,因此一般称为系统(System)加载器

  1. 自定义类加载器

除了由 Java 核心类库提供的类加载器外,我们还可以加入自定义的类加载器,来实现特殊的加载方式。举例来说,我们可以对 class 文件进行加密,加载时再利用自定义的类加载器对其解密。

JAVA_HOME 目录里面的内容

之所以写这个是因为平时开发中很少有人翻开这个文件夹来看,上面讲到这个目录顺便带着大家来看看。

Java类的生命周期,能说出来算你牛!

JAVA_HOME/bin目录放的很多命令

Java类的生命周期,能说出来算你牛!

JAVA_HOME/lib目录

Java类的生命周期,能说出来算你牛!

JAVA_HOME/jre/lib目录

Java类的生命周期,能说出来算你牛!

JAVA_HOME/jre/lib/ext目录

双亲委任

Java类的生命周期,能说出来算你牛!

双亲委任工作流程

双亲委派机制的工作流程:

  1. 当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。

每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。

  1. 当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.

  2. 当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。

Java类的生命周期,能说出来算你牛!

为什么需要双亲委任安全机制?

  1. 直观理解

试想一下黑客自定义一个 java.lang.String 类,该 String 类具有系统的 String 类一样的功能,只是在某个函数稍作修改。这个函数经常使用,假如在这这个函数中植入一些“病毒代 码”。并且通过自定义类加载器加入到 JVM 中。完了,程序凉凉,这是比较直观的理解。

  1. 真实原因

要完全理解这个问题还需要引入一个概念,类的命名空间

类需要类的全限定名(类的全路径)以及加载此类的ClassLoader来共同确定。也就是说即使两个类的全限定名是相同的,但是因为不同的ClassLoader加载了此类,那么在JVM中它是不同的类。

比如上面说的,我们 JDK 本生提供的类库,比如 string,hashmap,linkedlist 等等,这些类由bootstrp 类加载器加载了以后,无论你程序中有多少个类加载器,那么这些类其实都是可以共享的,这样就避免了不同的类加载器加载了同样名字的不同类以后造成混乱。

概括:

  1. 检查顺序:自底向上

  2. 加载顺序:自顶向下

Java类的生命周期,能说出来算你牛!

连接(Linking)


验证阶段

当一个类被加载之后,必须要验证一下这个类是否合法,比如这个类是不是符合字节码的格式、变量与方法是不是有重复、数据类型是不是有效、继承与实现是否合乎标准等等。

我们平常写代码很多时候第一步都是写校验,jvm也是这个思路,Java 编译器生成的类文件必然满足 Java 虚拟机的约束条件,但是为了防止“解字节码注入”。

验证阶段大致会完成下面四个阶段的检验动作:

  • 文件格式验证 (主要验证是否符合Class文件格式规范,并且能被当前版本的虚拟机处理。)

基于二进制字节流进行验证,只有通过了这个阶段的验证后,字节流才会进入内存的方法区中进行存储,所以后面的验证阶段全是基于方法区的存储结构进行的,不会再直接操作字节流。

  • 元数据验证(对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求)

如验证这个类是否有父类(除了java.lang.Object是所有类的父类),如果这个类不是抽象类是否实现了父类或者接口中要求实现的所有方法等。

  • 字节码验证

  • 符号引用验证(发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段解析阶段中发生)

如验证符号引用中通过字符串描述的全限定名是否能找到对应的类。

准备阶段

就是为类的静态变量分配内存并设为 jvm 默认的初值,而不是我们设置的,我们设置的会在后面一个阶段“初始化”期间来做,对于非静态的变量,则不会为它们分配内存。

jvm默认的初值是这样的:

基本类型(int、long、short、char、byte、boolean、float、double)的默认值为0。其中boolean只有true,false两种类型,对应到jvm值分别是数据1,0。

引用类型(对象,数组)的默认值为null。

构造其他跟类层次相关的数据结构,比如说用来实现虚方法的动态绑定的方法表。

在 class 文件被加载至 Java虚拟机之前,这个类无法知道其他类及其方法、字段所对应的具体地址,甚至不知道自己方法、字段的地址。因此,每当需要引用这些成员时,Java 编译器会生成一个符号引用。在运行阶段,这个符号引用一般都能够无歧义地定位到具体目标上(因为验证阶段进行符号引用验证了)。

例外:public static final int value=123,常量直接赋值为设置的123.

解析阶段

上面说到的“在运行阶段,这个符号引用一般都能够无歧义地定位到具体目标上”,就是在解析阶段进行的符号解析。

这个阶段目的正是将常量池中的符号引用转换解析成为实际引用。在解析阶段,jvm会将所有的类或接口名、字段名、方法名转换为具体的内存地址,从而让用到了别的类或者接口的类能找到和加载其他的类/接口。

如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载(但未必触发这个类的链接以及初始化)

Java类的生命周期,能说出来算你牛!

初始化


在 Java 代码中,如果要初始化一个静态字段,我们可以在声明时直接赋值,也可以在静态代码块中对其赋值。除了 final static 修饰的常量,直接赋值操作以及所有静态代码块中的代码,则会被 Java 编译器置于同一方法中,并把它命名为 < clinit >

类加载的最后一步是初始化,目的便是为标记为常量值的字段赋值,以及执行< clinit > 方法的过程。Java 虚拟机会通过加锁来确保类的 < clinit > 方法仅被执行一次。

类初始化的七种触发情况:

  1. 当虚拟机启动时,初始化用户指定的主类(main函数);

  2. 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类;

  3. 当遇到调用静态方法的指令时,初始化该静态方法所在的类;

  4. 子类的初始化会触发父类的初始化;

  5. 如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化;

  6. 使用反射 API 对某个类进行反射调用时,初始化这个类;

  7. 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。

设计模式中单例延迟加载,便是充分利用了这个特点。

Java类的生命周期,能说出来算你牛!

卸载


那么多的类,什么时候卸载呢?关于卸载谁,满足如下条件:

  1. 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例;

  2. 加载该类的ClassLoader已经被回收;

  3. 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。

关于什么时候卸载,当以上条件都满足了,垃圾回收时候回在方法区清空类信息进行卸载,英雄迟暮,这个类的一生也就走到了尽头了。

Java类的生命周期,能说出来算你牛!

Java类的生命周期,能说出来算你牛!

点个 在看,赞👍支持我吧

Java类的生命周期,能说出来算你牛!

本文分享自微信公众号 - 码农沉思录(code-thinker)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解2016年09月02日00:00:36 \牧野(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fme.csdn.net%2Fdcrmg) 阅读数:59593
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
3年前
Github标星5300+,专门为程序员开发文档开源管理系统,我粉了
!(https://oscimg.oschina.net/oscnet/a11909a041dac65b1a36b2ae8b9bcc5c432.jpg)码农那点事儿关注我们,一起学习进步!(https://oscimg.oschina.net/oscnet/f4cce1b7389cb00baaab228e455da78d0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Nginx反向代理upstream模块介绍
!(https://oscimg.oschina.net/oscnet/1e67c46e359a4d6c8f36b590a372961f.gif)!(https://oscimg.oschina.net/oscnet/819eda5e7de54c23b54b04cfc00d3206.jpg)1.Nginx反
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这