Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2019-05-05:简述JVM中类的加载机制与加载过程? #44

Open
Shanlovana opened this issue Apr 30, 2019 · 9 comments
Open

2019-05-05:简述JVM中类的加载机制与加载过程? #44

Shanlovana opened this issue Apr 30, 2019 · 9 comments
Labels

Comments

@Shanlovana
Copy link
Collaborator

No description provided.

@Shanlovana Shanlovana changed the title 简述JVM中类的加载机制与加载过程 2019-05-01:简述JVM中类的加载机制与加载过程 Apr 30, 2019
@canyie
Copy link

canyie commented May 1, 2019

因为没说是什么JVM,这里假设hotspot
因为我也是小白,不保证正确
1.当要使用类时,JVM会判断此类是否已被加载到当前环境里,没有则会调用当前环境的ClassLoader.loadClass获得类
2.一般情况下,ClassLoader先尝试查找当前ClassLoader已加载的类,找不到则委托给父ClassLoader,通过双亲委派机制一层一层的往上传递,直到BootClassLoader(当然不一定要遵守此规则)
3.如果父ClassLoader无法加载此类,调用findClass方法加载,此时进入正题
4.findClass去找对应的.class文件(android里是去dex文件里找对应的类),找不到就抛ClassNotFoundException,然后加载到内存中
5.加载到内存中以后,首先校验格式(比如.class文件默认有8个字节的魔术字等),然后判断是否需要链接
6.如果需要链接,对类进行解析及初始化,比如初始化静态变量,初始化静态方法,执行静态代码块等,此阶段发生异常会引发LinkageError/ExceptionInInitializerError
7.如果走到这里一切都好,类加载的很成功,就返回Class,否则加载失败
参考资料:JVM规范-类的加载,链接和初始化

最后,五一快乐

@ADrunkenLiBai
Copy link

因为没说是什么JVM,这里假设hotspot
因为我也是小白,不保证正确
1.当要使用类时,JVM会判断此类是否已被加载到当前环境里,没有则会调用当前环境的ClassLoader.loadCLass获得类
2.一般情况下,ClassLoader先尝试查找当前ClassLoader已加载的类,找不到则委托给父ClassLoader,通过双亲委派机制一层一层的往上传递,直到BootClassLoader(当然不一定要遵守此规则)
3.如果父ClassLoader无法加载此类,调用findClass方法加载,此时进入正题
4.findClass去找对应的.class文件(android里是去dex文件里找对应的类),找不到就抛ClassNotFoundException,然后加载到内存中
5.加载到内存中以后,首先校验格式(比如.class文件默认有8个字节的魔术字等),然后判断是否需要链接
6.如果需要链接,对类进行解析及初始化,比如初始化静态变量,初始化静态方法,执行静态代码块等,此阶段发生异常会引发LinkageError/ExceptionInInitializerError
7.如果走到这里一切都好,类加载的很成功,就返回Class,否则抛异常(ClassNotFoundException/NoClassDefFoundError/ClassFormatError/LinkageError/ExceptionInInitializerError等等)

参考资料:JVM规范-类的加载,链接和初始化

最后,五一快乐

jvm跟DVM有所区别吗

@Moosphan Moosphan changed the title 2019-05-01:简述JVM中类的加载机制与加载过程 2019-05-05:简述JVM中类的加载机制与加载过程? May 5, 2019
@MoJieBlog
Copy link
Collaborator

MoJieBlog commented May 5, 2019

下面是我之前看深入理解JVM记得笔记。直接复制了。

1.1 概述

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟直接使用的java类型,这就是虚拟机的类加载机制。

1.2 类加载的过程

  • 加载
  • 验证
  • 准备
  • 解析
  • 初始化
  • 使用
  • 卸载

其中验证,准备,解析三个部分称为连接。其中解析和初始化的顺序可能会不同(可能先解析后初始化,也可能先初始化后解析)。

1.2.1 关于初始化

5种情况会触发类的初始化

  • 遇到new,getstatic,putstatic,invokesstatic这四个字节码指令时,如果类没有被初始化
  • 使用java.lang.reflect包的方法对类进行反射时,如果类没有被初始化,则先触发其初始化
  • 当初始化一个类时,其父类没有被初始化,则需要父类先初始化
  • 虚拟机启动时,用户需要制定一个执行的主类,虚拟机会先初始化这个类
  • JDK 1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例后的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有被初始化

1.3 类加载的过程

  • 通过一个类的全限名来获取定义此类的二进制字节流
  • 将字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

这里顺带再说下对象的加载过程。

1. 对象的创建

  • java虚拟机遇到一个new指令
  • 检查new引用代表的类是否被加载,解析和初始化
    • 加载过
    • 没有加载过,先执行相应类的加载过程
  • 虚拟机为对象分配内存
    • 对象所需要的内存大小在类加载过后便可以确定
    • 为对象分配空间的过程等同于把一块确定大小的内存从java堆中划分出来。
      • java堆绝对规整
        • 绝对规整解释:所有用过的内存放在一边,空闲的内存放在另一边中间放着一个指针作为分界点的指示器。
        • 分配过程:指针向空闲空间那边挪动一段与对象大小相等的距离。这种分配方式称为“指针碰撞”。
      • java堆不规整
        • 不规整解释:已使用的内存和未使用的内存相互交错,虚拟机维护一个列表,记录那些内存是可用的。
        • 分配过程:分配时从列表中找一块足够大的空间划分给对象实例。并更新列表上的记录。这种分配方式称为“空闲列表”。
      • java堆是否规整是由所采用的垃圾收集器是否带有压缩整理功能决定的。
  • 对象创建时并发问题
    • 描述:对象创建在虚拟机中是非常频繁的,因此在并发情况下是线程不安全的。可能指针正在为A对象分配内存,对象B又同时使用了原来的指针来分配内存。
    • 解决方案
      • 第一种方式:
        • 分配内存的动作进行同步处理
      • 第二种方式:
        • 把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲。哪个线程要分配内存就在哪个线程的TLAB上分配。只有TLAB用完并分配新的TLAB时才需要同步锁定。
        • TLAB解释:TLAB全称ThreadLocalAllocBuffer,是线程的一块私有内存,如果设置了虚拟机参数 -XX:UseTLAB,在线程初始化时,同时也会申请一块指定大小的内存,只给当前线程使用,这样每个线程都单独拥有一个Buffer,如果需要分配内存,就在自己的Buffer上分配,这样就不存在竞争的情况,可以大大提升分配效率,当Buffer容量不够的时候,再重新从Eden区域申请一块继续使用,这个申请动作还是需要原子操作的。
  • 虚拟机将分配到的内存空间都初始化为零值(不包括对象头),这一操作保证了对象的实例字段在java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值
  • 虚拟机对对象进行必要的设置,例如这个对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等信息。这些信息存在对象头(Object Header)之中。
  • 执行对象初始化,即把对象按照程序员的意愿进行初始化。

2.对象的访问定位

  • 前面的学习我们知道,java虚拟机栈保存的是对象的句柄或者对象地址。所以访问对象需要通过栈上的局部变量表(reference)来操作堆上的具体对象。因为reference只是一个只想对象的引用,并没有定义这个引用应该通过什么方式定位,访问堆中的对象的具体位置,所以对象访问的方法取决于虚拟机的实现方式。目前主流的访问方式有以下两种
    • 句柄式:
      • java堆中有一块内存作为句柄池,reference存储的就是对象的句柄地址。句柄包含了对象实例数据与类型数据各自具体的地址信息。
      • 优点:对象被移动时(垃圾回收时,整理内存时,很有可能被移动)只会改变句柄中的实例数据指针,不用改变reference
    • 直接指针访问:
      • reference存储的直接就是对象地址
      • 优点:快,省略了一次句柄到具体位置的访问时间。因为对象比较多,所以这个时间很可观

@Petterpx
Copy link

Petterpx commented May 5, 2019

嗯,上面大佬们都说的差不多了。
先带上一张图
image
Java中的类加载过程为,加载-连接-初始化
那类加载是什么意思呢?
类加载指的是class文件读入内存,并为其创建一个Class对象,也就是程序使用任何一个类的时候,都会为其创建一个java.lang.Class对象。

连接是什么意思呢?

连接阶段主要负责将类的二进制数据合并到jre中。
--但是为什么要将数据合并到jre呢?
jre指的是java的运行时环境,也就是java虚拟机,因为各大平台不一样,也就是运行环境,所以java虚拟机相当于屏蔽了这些条件,这样java程序就可以实现编译一次,处处运行。也就是我们刚开始学java时的第一句话。
初始化呢?
简单来说,虚拟机负责对类进行初始化,其实也就是对变量进行初始化,初始化的时候,遵循以下3个条件:

  1. 假如这个类没有被加载和连接,则程序先加载并连接该类。
  2. 假如该类的直接父类还没有被初始化,则先初始化其直接父类。
  3. 假如该类中有初始化语句,则系统一次执行这些初始化语句。
    不过需要注意一点,当调用被final修饰的类变量时(基本类型),并不会引起类的初始化。

类加载机制的话,主要有以下3种:
全盘负责:
双亲委托:
缓存机制:

双亲委托的话,用一个简单栗子来解释。
比如说你自己定义了一个java.lang.String类,但是你永远调用不了它,为什么呢?
因为String属于核心类,在类加载的过程中,由引导类加载器去加载,所以永远用不了自定义的这个类。
双亲委托机制也就是,当一个类加载器收到类加载请求后,他不会先去加载,而是让它的父加载器先去尝试,层层向上,只有父类无法加载,这时才会尝试让子类去加载,这也就是自己写的为啥无法加载。
那缓存机制是什么呢?
缓存机制保证我们加载过的类都会被缓存,这样当程序使用某个类时,首先从缓存区中去找,当找到了直接返回Class对象,避免了重复加载。这也就是为什么修改了代码,需要重新编译运行才会生效的原因了。
如果有什么问题,也欢迎各位大佬们补充:

@yangfanggang
Copy link

java中的类加载机制

Java语言系统自带有三个类加载器: 

  • Bootstrap ClassLoader 最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、
    resources.jar、charsets.jar和class等。另外需要注意的是可以通过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。比如java -Xbootclasspath/a:path被指定的文件追加到默认的bootstrap路径中。
  • Extention ClassLoader 扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还
    可以加载-D java.ext.dirs选项指定的目录。 
  • Appclass Loader也称为SystemAppClass 加载当前应用的classpath的所有类。

我们需要知道这三个加载器的加载顺序

  1. Bootstrap CLassloder

  2. Extention ClassLoader

  3. AppClassLoader

然后需要知道这个加载顺序具体执行策略 双亲委托机制

一个类加载器查找class和resource时,是通过“委托模式”进行的,它首先判断这个class是不是已经加载成功,如果没有的话它并不是自己进行查找,而是先通过父加载器,然后递归下去,直到Bootstrap ClassLoader,如果Bootstrap classloader找到了,直接返回,如果没有找到,则一级一级返回,最后到达自身去查找这些对象。这种机制就叫做双亲委托。

具体参见 我的博客

@canyie
Copy link

canyie commented May 6, 2019

@ADrunkenLiBai 回答你那个问题:
在JVM规范里,任何能够加载与执行Java 字节码(即class文件)的虚拟机都能叫JVM
但是Dalvik VM略有不同,其并没有完全遵守JVM规范,比如它不能执行class,只能加载dex文件格式(个人推测是害怕侵权?)

@Moosphan Moosphan added the Java label May 7, 2019
@LvKang-insist
Copy link

类加载的过程分为三个部分:
1,加载
类加载是将.class 文件中的二进制数据读入内存中,将其放在运行时数据区的方法区内,然后在堆内存中创建一个对象,又来封装类在方法区内的数据接口。类加载的最终产品是为与堆中的Class 对象。
2,类的连接
类加载完成后,就会进入连接阶段,连接阶段负责把类的二进制数据合并到jre中,可分为三个阶段
1,验证。
2,准备。
3,解析
3,类的初始化
在类的初始化阶段,虚拟机负责对类进行初始化,主要是对类变量进行初始化。JVM初始化一个类包含如下几个步骤
1. 假如这个类没有被加载和连接,则程序加载并连接该类。
2. 假如该类的直接父类还没有被初始化,则先初始化其直接父类。
3. 假如类中有初始化语句,则系统一次执行这些初始化语句。

   当执行第二步时,系统对直接的父类的初始化步骤也遵循此步骤。如果该类又有直接父类,则系统会再次重复这三个步骤来初始化该类。所以最先初始化的总是java.long.Object类。

@yline
Copy link

yline commented Jan 6, 2020

    加载机制:
        1,缓存机制,先从缓存区搜索
        2,全盘负责,当一个类加载器负责加载某个class,则Class所依赖和引用的其他的Class也由它负责,除非显示使用另一个。
        3,双亲委派,先让父加载器试图加载,只有当父加载器无法加载时,才尝试从自己的路径加载

    加载过程:[2,3,4统一称为连接]
        1,加载:获取到Class文件、转化为方法区的数据结构,并创建一个引用对象
        2,验证:验证Class文件的合法性
        3,准备:分配类变量内存,并设置"初始化值";(static 为0,static final 为ContentValue值)
        4,解析:将符号引用转化为直接引用
        5,初始化:将直接引用的类变量执行clinit方法,进行赋值

@senlinxuefeng
Copy link

通俗易懂的文章
图解JVM类加载机制与类加载过程

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

10 participants