一、创建类的整体过程

image.png
从上图,我们可以知道new完对象后,必须先进行类的加载检查,那么什么是类的加载检查呢?从JVM的角度来说,JVM拿到了new对象的指令之后,会去常量池进行查找相关的符号引用,如果没有找到,就会进行类的加载过程(加载、链接(验证、准备、解析)、初始化);而相应的,如果找到了该符号引用,就可以直接使用并给其分配所需的内存。

  1. 分配内存,看似挺简单的一些操作(从Java堆里面划分出一块内存),事实上JVM对其是有相应的机制的。JVM对于内存分配有两种方式,分别是“指针碰撞”和“空闲列表”。接下来,稍微解释一下这两种方式的含义:
  • “指针碰撞”:说白了就是把整个堆划分为两块区域,一块是已经被分配了,另一块是是空闲的,而在这两块区域的中间就插着一个指针。当有需要分配内存时,就会按照所需的内存大小像空闲区域移动。
  • “空闲列表”:指的是当Java的堆并不是整齐区分为两块区域,而是已使用的区域块和未使用的区域块杂乱交错,这时候JVM会维护一张空闲列表,会把那些空闲块的地址放在该列表进行管理,当有需要内存时,就会划分出一块连续的区域出去。

    1. 接下来,就是初始化<init>操作了,这一步骤只是给所分配到的内存空间初始化为默认值,比如**int类型的常量先初始化为0**。至于初始化为设置值的操作,则是在最后一步进行的,执行<init>方法之后,就会赋上对应的值。
    2. 看了上图,估计读者会提一嘴什么是对象头呢?<br /> 顾名思义,对象头就是存储着这个对象的的相关信息,比如它是哪个类的实例,对象的年龄信息等等。注意,在对象头这个区域,JVM默认使用了指针压缩的机制。<br /> 所谓的指针压缩,也就是将相应指针的大小进行一些缩小,将64位的指针压缩为32位的指针,较少内存的消耗,降低GC的压力。当然,指针压缩也是可以关闭的,但是建议不要关闭。

    二、类的加载过程

    类的加载是 创建类步骤中的第一步 在这里静态方法就会执行了

类的加载过程包括加载、链接(包括验证、准备、解析)、初始化五个阶段。这几个阶段发生的顺序是确定的,但是解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始。注意这几个阶段是按顺序开始,不是按顺序进行或完成,这些阶段通常都是互相交叉地混合进行,即在一个阶段执行的过程中调用或激活另一个阶段。

  • 加载:获取类的二进制字节流,将字节流所代表的今天存储结构转化为方法区的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

  • 验证:确保被加载的类的正确性,如字节流是否符合Class文件格式的规范、对字节码描述的信息进行语义分析、数据流和控制流分析确定程序语义是合法并符合逻辑的、确保解析动作能正确执行。

  • 准备:为类的静态变量在方法区中分配内存,不包括实例变量,实例变量会在对象实例化的时候随对象一块分配到Java堆中。这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显示地赋予的值,因为这个时候尚未开始执行任何Java方法,而静态赋值语句是在程序编译后,存放于类构造器方法之中的,所以静态赋值语句将在初始化阶段才会执行。

  • 解析:虚拟机将常量池内的符号引用替换为直接引用的过程,主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以使用任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

  • 初始化:为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化,也就是执行静态代码,而且以后不会再执行这段静态代码了。在Java中对类变量进行初始值设定有两种方式:①声明类变量是指定初始值②使用静态代码块为类变量指定初始值。

三、Class.forName() 和 ClassLoader

3.1 class.forName()

首先java里面任何class都要加载在虚拟机上才能运行,forName()方法就是加载类的,并且通过Class.forName(“Package.A”).newInstance()可以得到和new A()一样的效果,也就是说第一种方法就是把第二种方法分解成了两步,即先调用 forName 方法加载类,然后实例化,而第二种方法时把这两步合并了。

Class.forName(String name)该方法内部调用另一个重载方法forName(String name, boolean initialize, ClassLoader loader),这个重载方法使用给定的ClassLoader加载类并返回类的Class对象。这个重载方法加载类的过程包括加载、链接,而且它的第二个参数可以控制是否进行初始化,也就是是否执行static块,静态方法。

  1. @CallerSensitive
  2. public static Class<?> forName(String className)
  3. throws ClassNotFoundException {
  4. Class<?> caller = Reflection.getCallerClass();
  5. // forName0 是一个native 方法
  6. return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
  7. }
  8. /** Called after security check for system loader access checks have been made. */
  9. private static native Class<?> forName0(String name, boolean initialize,
  10. ClassLoader loader,
  11. Class<?> caller)
  12. throws ClassNotFoundException;

3.2 ClassLoader.loadClass()

  1. public class Test08 {
  2. public static void main(String[] args) throws ClassNotFoundException {
  3. s();
  4. }
  5. public static void s() throws ClassNotFoundException {
  6. ClassLoader classLoader = Test08.class.getClassLoader();
  7. classLoader.loadClass("Woman");
  8. }
  9. }

loadClass()方法得到的Class对象是还没有链接的,适合在类加载时不需要一些初始化的情况,即不会执行静态代码块

3.3 为什么有了new,还要使用Class.forName().newInstance()

这主要考虑到软件的可伸缩、可扩展和可重用等软件设计思想。

Java中工厂模式经常使用newInstance()方法来创建对象。例如:
class c = Class.forName(“Student”);
factory = (StudentInterface)c.newInstance();

其中StudentInterface是Student的接口,上述代码可以写成如下形式:
String className = “Student”;
class c = Class.forName(“className”);
factory = (StudentInterface)c.newInstance();

进一步可写成如下形式:
String className = readfromXMLConfig;
class c = Class.forName(className);
factory = (StudentInterface)c.newInstance();

这样,上述代码就不存在Student的类名了,无论你的className怎么变化,只要实现了StudentInterface,上述代码就可以运行。这就达到了解耦、可扩展性。

最后感谢:

https://blog.csdn.net/Mr_Police/article/details/122650776
https://blog.csdn.net/XG1057415595/article/details/85331456