class 的加载过程

一、类加载的过程

在这里插入图片描述
加载: 把一个个class(二进制文件) 加载到内存。
校验: 校验加载的文件是否符合class 文件的标准。
准备: 把静态变量赋默认值,并非初始值。如public static int v = 0;
解析: 虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的:

  1. CONSTANT_Class_info
  2. CONSTANT_Field_info
  3. CONSTANT_Method_info
    等类型的常量。

初始化: 静态变量赋值为初始值。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。

二、类加载器

1. 加载过程理论

在这里插入图片描述
上图中的各个加载器维护自己的一个缓存,当最底层的类加载器需要加载class 到内存的时候会一层一层的访问父加载器看是否已经加载过,如果都没有加载过最后会反馈给最底层的加载器进行加载。这个过程是已经写死的,只能实现查找的类的部分,这就是双亲委派机制。如果最后加载失败会报异常ClassNotFoundException。

双亲委派的目的: 安全问题

容易混淆:

  1. 上图中的各种加载器并非是继承关系。
  2. 父加载器不是“类加载器的加载器”,也不是“类加载器的父类加载器”。
  3. Bootstrap 是c++ 实现的模块,并没有类与之对应,因此Ext 在getClassLoader 的时候会返回一个null 值。

2. 加载目录实例

public class ClassLoaderLevel {
    public static void main(String[] args) {
        System.out.println(ClassLoaderLevel.class.getClassLoader());
        System.out.println(ClassLoaderLevel.class.getClassLoader().getParent());
        System.out.println(ClassLoaderLevel.class.getClassLoader().getParent().getParent());
    }
}
打印结果:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@39c0f4a
null

Launcher 是ClassLoader 的一个包装类(启动类),AppClassLoader 及ExtClassLoader 是该类下的内部类

=============================================
Launchar 中的部分代码:
public class Launcher {
    private static URLStreamHandlerFactory factory = new Launcher.Factory();
    private static Launcher launcher = new Launcher();
    private static String bootClassPath = System.getProperty("sun.boot.class.path");
    private ClassLoader loader;
    private static URLStreamHandler fileHandler;
......
=============================================
static class AppClassLoader extends URLClassLoader {
    final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);

    public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
        final String var1 = System.getProperty("java.class.path");
        final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
        return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
            public Launcher.AppClassLoader run() {
                URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                return new Launcher.AppClassLoader(var1x, var0);
            }
        });
    }
......
=============================================

static class ExtClassLoader extends URLClassLoader {
   private static volatile Launcher.ExtClassLoader instance;
    private static File[] getExtDirs() {
        String var0 = System.getProperty("java.ext.dirs");
        File[] var1;
        if (var0 != null) {
......
=============================================

3. 验证类加载器的路径

System.out.println("---------pathBoot-------");
String pathBoot = System.getProperty("sun.boot.class.path");
System.out.println(pathBoot.replaceAll(";", System.lineSeparator()));

System.out.println("---------pathExt-------");
String pathExt = System.getProperty("java.ext.dirs");
System.out.println(pathExt.replaceAll(";", System.lineSeparator()));

System.out.println("---------pathApp-------");
String pathApp = System.getProperty("java.class.path");
System.out.println(pathApp.replaceAll(";", System.lineSeparator()));

打印结果:
---------pathBoot-------
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\resources.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\rt.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\sunrsasign.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\jsse.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\jce.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\charsets.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\jfr.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\classes
---------pathExt-------
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
---------pathApp-------
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\charsets.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\deploy.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\ext\access-bridge-64.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\ext\cldrdata.jar
......
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\ext\sunjce_provider.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\ext\sunmscapi.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\ext\sunpkcs11.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\ext\zipfs.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\javaws.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\jce.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\jfr.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\jfxswt.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\jsse.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\management-agent.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\plugin.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\resources.jar
D:\empolder\jdk\jdk1.8\jdk1.8.0_171\jre\lib\rt.jar
D:\workSpace(old-svn)\BaseAdmin\target\classes
D:\empolder\apache-maven-3.5.4\repo\cn\hutool\hutool-all\5.3.5\hutool-all-5.3.5.jar
D:\empolder\apache-maven-3.5.4\repo\org\springframework\boot\spring-boot-starter-jdbc\1.5.9.RELEASE\spring-boot-starter-jdbc-1.5.9.RELEASE.jar
......

4. 类加载过程

public static void main(String[] args) throws ClassNotFoundException {
    Class<?> aClass = ClassLoaderLevel.class.getClassLoader().loadClass("com.iscas.baseAdmin.jvm.ClassLoaderLevel");
    System.out.println(aClass.getName());
}

输出结果:
com.iscas.baseAdmin.jvm.ClassLoaderLevel

先到硬盘上找到源码,load 到内存,与此同时生成一个class 类的对象,并将其返回。
public Class<?> loadClass(String var1) throws ClassNotFoundException {
 	return this.loadClass(var1, false);
}
protected Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
    synchronized(this.getClassLoadingLock(var1)) {
    	 //是否已经加载,没有加载返回findLoadedClass0
		 //protected final Class<?> findLoadedClass(String var1) {
		 //		return !this.checkName(var1) ? null : this.findLoadedClass0(var1);
		 //}
		 //private final native Class<?> findLoadedClass0(String var1); 只能读Hotspot 源码了
    private final native Class<?> findLoadedClass0(String var1);

         Class var4 = this.findLoadedClass(var1);
         if (var4 == null) {
             long var5 = System.nanoTime();

             try {
             	 //开始委派过程
                 if (this.parent != null) {
                     var4 = this.parent.loadClass(var1, false);
                 } else {
                     var4 = this.findBootstrapClassOrNull(var1);
                 }
             } catch (ClassNotFoundException var10) {
             }
			 //父加载器都没有找到
             if (var4 == null) {
                 long var7 = System.nanoTime();
                 var4 = this.findClass(var1);
                 PerfCounter.getParentDelegationTime().addTime(var7 - var5);
                 PerfCounter.getFindClassTime().addElapsedTimeFrom(var7);
                 PerfCounter.getFindClasses().increment();
             }
         }

         if (var2) {
             this.resolveClass(var4);
         }

         return var4;
     }
 }
 
//父加载器都没有找到,自己调用该类,protected 只能在子类访问,所以自定义ClassLoader 只需重写findClass,
//父加载器把逻辑全部定义了,只有中间一部分交给子类来实现,这是模板模式,每个父加载器都有findClass。
protected Class<?> findClass(String var1) throws ClassNotFoundException {
    throw new ClassNotFoundException(var1);
}

5. 找各个加载器的findClass(ExtClassLoader 为例)

在这里插入图片描述
在这里插入图片描述

6. 自定义ClassLoader

/**
 * 自定义ClassLoader 在写类库和框架的时候经常用到
 */
public class MyClassLoader extends ClassLoader{

    private int b;

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File f = new File("c://test/", name.replaceAll(".","/").concat(".class"));
        try {
            FileInputStream fis = new FileInputStream(f);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            b = 0;

            while ((fis.read()) != 0){
                baos.write(b);
            }

            byte[] bytes = baos.toByteArray();
            bytes.clone();
            fis.close();

            return defineClass(name, bytes, 0, bytes.length);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.findClass(name);
    }

    //自定义ClassLoader 的用法
    public static void main(String[] args) throws Exception {
        ClassLoader classLoader = new MyClassLoader();
        Class<?> aClass = classLoader.loadClass("com.xxx.xxx");
        Object o = aClass.newInstance();
        o.toString();

        System.out.println(classLoader.getClass().getClassLoader());
        System.out.println(classLoader.getParent());
    }
}

7. 混合执行,编译执行,解释执行

7.1 解释器
  • bytecode interrupter
7.2 JIT
  • Just In-Time Compiler
7.3 混合模型
  • 混合模型使用解释器 + 热点代码编译
  • 起始阶段采用解释执行
  • 热点代码检测
    多次被调用的方法(方法计数器:检测方法执行频率);
    多次被调用的循环(循环计数器:检测循环执行频率);
    进行编译

-Xmixed:默认为混合模式,开始解释执行,启动速度较快,对热点代码实行检测和编译;
-Xint:使用解释型模式,启动很快,执行稍慢;
-Xcomp:使用纯编译模式,执行很快,启动很慢。

三、双亲委派的打破

1. 如何打破?

loadClass 已经被写死了,除非能够重写loadClass。

2. 合适打破?

  • JDK1.2 之前,自定义ClassLoader 都必须重写loadClass()。重写findClass 是打破不了双亲委派的;
  • ThreadContextClassLoader 可以实现基础类调用实现类代码,通过thread.setContextClassLoader 指定;
  • 热启动、热部署:osgi tomcat 都有自己的模块指定classloader(可以加载同一类库的不同版本)。

四、硬件层数据一致性

  • 现代CPU 数据一致性实现 = 缓存锁(MESI…)+ 总线锁。
  • 缓存行(cache line):将数据从总线读到CPU 缓存时读取一行的数据而非只是需要的那部分数据,目的是为了提高效率。目前多数是64位。
  • 伪共享:位于同一行的两个不同数据,被两个不同CPU锁定,产生互相影响的伪共享。
//缓存行的例子
public class CacheLinePadding_01 {
    private static class T{
        public volatile long x = 0L;
    }
    public static T[] arr = new T[2];

    static {
        arr[0] = new T();
        arr[1] = new T();
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (long i = 0; i < 1000_0000L; i++){
                arr[0].x = i;
            }
        });
        Thread t2 = new Thread(() -> {
            for (long i = 0; i < 1000_0000L; i++){
                arr[0].x = i;
            }
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println((System.nanoTime() - start)/100_0000L);
    }
}
//==============================
public class CacheLinePadding_02 {
    private static class Padding{
        public volatile long p1, p2, p3, p4, p5, p6, p7;
    }

    private static class T extends Padding{
        public volatile long x = 0L;
    }

    public static T[] arr = new T[2];

    static {
        arr[0] = new T();
        arr[1] = new T();
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (long i = 0; i < 1000_0000L; i++){
                arr[0].x = i;
            }
        });
        Thread t2 = new Thread(() -> {
            for (long i = 0; i < 1000_0000L; i++){
                arr[0].x = i;
            }
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println((System.nanoTime() - start)/100_0000L);

    }
}

链接: MESI–CPU缓存一致性协议.

五、乱序问题

  • CPU 为了提高指令执行效率,会在一条指令执行的过程中(比如去内存读取数据(慢100 倍)),去同时执行另一条指令,前提是,两条指令没有依赖关系。

链接: CPU 乱序执行的根源.

//乱序代码验证
public class InterruptedException {
    private static int x = 0, y = 0;
    private static int a = 0, b = 0;

    public static void main(String[] args) throws java.lang.InterruptedException {
        int i = 0;
        for (;;){
            i++;
            x = 0; y = 0;
            a = 0; b = 0;
            Thread one = new Thread(new Runnable() {
                @Override
                public void run() {
                    shortTime(1000000);
                    a = 1; x = b;
                }
            });
            Thread other = new Thread(new Runnable() {
                @Override
                public void run() {
                    b = 1; y = a;
                }
            });
            one.start();other.start();
            one.join();other.join();
            String result = "第" + i + "次(" + x + "," + y + ")";
            if (x == 0  && y == 0){
                System.err.println(result);
                break;
            }else {
            }
        }
    }
    public static void shortTime(long interval){
        long start = System.nanoTime();
        long end;
        do{
            end = System.nanoTime();
        }while (start + interval >= end);
    }
}

六、如何保证特定情况下不乱序

1. 硬件内存屏障 X86

fence [fɛns] 栅栏,篱笆; 围墙; 防护物; 剑术;

  • sfence: store| 在sfence指令前的写操作当必须在sfence指令后的写操作前完成。
  • lfence:load | 在lfence指令前的读操作当必须在lfence指令后的读操作前完成。
  • mfence:modify/mix | 在mfence指令前的读写操作当必须在mfence指令后的读写操作前完成。

2. JVM级别如何规范(JSR133)

  • LoadLoad屏障:
对于这样的语句Load1; LoadLoad; Load2, 
在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
  • StoreStore屏障:
对于这样的语句Store1; StoreStore; Store2,
在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
  • LoadStore屏障:
对于这样的语句Load1; LoadStore; Store2,
在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
  • StoreLoad屏障:
对于这样的语句Store1; StoreLoad; Load2,
在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。

3. volatile的实现细节

  • 字节层面
    ACC_VOLATILE

  • JVM层面
    volatile内存区的读写 都加屏障

StoreStoreBarrier
volatile 写操作
StoreLoadBarrier

LoadLoadBarrier
volatile 读操作
LoadStoreBarrier

  • OS和硬件层面
    https://blog.csdn.net/qq_26222859/article/details/52235930
    hsdis - HotSpot Dis Assembler
    windows lock 指令实现 | MESI实现

4. synchronized实现细节

  1. 字节码层面
    ACC_SYNCHRONIZED
    monitorenter monitorexit
  2. JVM层面
    C C++ 调用了操作系统提供的同步机制
  3. OS和硬件层面
    X86 : lock cmpxchg / xxx
    https😕/blog.csdn.net/21aspnet/article/details/

5. happens-before 原则(JVM规定重排序必须遵守的规则)

  1. 程序的秩序规则:同一个线程内,按照代码出现的顺序,前面的代码先于后面的代码。准确的说是控制流程,因为要考虑到分支循环结构。
  2. 管程锁定规则:一个unlock 操作先行发生于后面(时间上)对同一个锁的lock操作。
    volatile 变量规则:对一个volatile 变量的写操作先行发生于后面(时间上)对这个变量的读操作。
  3. 线程启动规则:Thread 的start() 方法先行发生于这个线程的每一个操作。
  4. 线程中断规则:对线程的interrupt() 方法的调用先行发生于被中断线程的代码检测到中断时间的发生,可以通过Thread.interrupt() 方法检测线程是否中断。
  5. 线程终止规则:线程的所有操作都先行于此线程的终止操作。可以通过Thread.join() 方法结束、Thread.isAlive() 的返回值等手段检测线程的终止。
  6. 对象终结规则:一个对象的初始化完成先行于发生它的finalize() 方法的开始。
  7. 传递性:如果操作A先行于操作B,操作B先行于操作C,那么操作A先行于操作C。

6. as if serial

不管如何重排序,单线程执行结果不会改变。

七、面试题

1. 对象的创建过程

2. 对象在内存中的存储布局

查看虚拟机的配置
打印version 时显示命令行所自带的参数:
java -XX:+PrintCommandLineFlags -version

-XX:InitialHeapSize=128168448   初始堆大小
-XX:MaxHeapSize=2050695168   最大堆大小
-XX:+PrintCommandLineFlags 
-XX:+UseCompressedClassPointers   ClassPointer指针,指向 new T();
-XX:+UseCompressedOops  实例数据
-XX:+UseParallelGC 
java version "1.8.0_212"
Java(TM) SE Runtime Environment (build 1.8.0_212-b10)
Java HotSpot(TM) 64-Bit Server VM (build 25.212-b10, mixed mode)

3. 对象的大小

对象的大小跟虚拟机的实现以及虚拟机的设置都有关系。
普通对象:

  1. 对象头:markword 8
  2. ClassPointer指针:-XX:+UseCompressedClassPointers 为4字节 不开启为8字节
  3. 实例数据
    1. 引用类型:-XX:+UseCompressedOops 为4字节 不开启为8字节
      Oops Ordinary Object Pointers
  4. Padding对齐,64位机器按照块读取,8的倍数

数组对象:

  1. 对象头:markword 8
  2. ClassPointer指针同上
  3. 数组长度:4字节
  4. 数组数据
  5. 对齐 8的倍数
package com.mashibing.jvm.c3_jmm;
   
import com.mashibing.jvm.agent.ObjectSizeAgent;

public class T03_SizeOfAnObject {
    public static void main(String[] args) {
        System.out.println(ObjectSizeAgent.sizeOf(new Object()));
        System.out.println(ObjectSizeAgent.sizeOf(new int[] {}));
        System.out.println(ObjectSizeAgent.sizeOf(new P()));
    }
	// 一个Object 占多少个字节
	//-XX:+UseCompressedClassPointers  -XX:+UseCompressedOops
	//Oops = ordinary object pointers
    private static class P {
                        //8 _markword
                        //4 _class pointer   +UseCompressedClassPointers
        int id;         //4
        String name;    //4 name 是一个引用,应占8个字节,但+UseCompressedOops
        int age;        //4

        byte b1;        //1
        byte b2;        //1

        Object o;       //4
        byte b3;        //1
    }
}

4. 对象头具体包括什么

5. 对象是怎么定位的

  1. 句柄池;
  2. 直接指针(Hotspot 使用了这种)

6. 对象怎么分配

GC 相关的内容

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值