class 的加载过程
一、类加载的过程
加载: 把一个个class(二进制文件) 加载到内存。
校验: 校验加载的文件是否符合class 文件的标准。
准备: 把静态变量赋默认值,并非初始值。如public static int v = 0;
解析: 虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的:
- CONSTANT_Class_info
- CONSTANT_Field_info
- CONSTANT_Method_info
等类型的常量。
初始化: 静态变量赋值为初始值。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。
二、类加载器
1. 加载过程理论
上图中的各个加载器维护自己的一个缓存,当最底层的类加载器需要加载class 到内存的时候会一层一层的访问父加载器看是否已经加载过,如果都没有加载过最后会反馈给最底层的加载器进行加载。这个过程是已经写死的,只能实现查找的类的部分,这就是双亲委派机制。如果最后加载失败会报异常ClassNotFoundException。
双亲委派的目的: 安全问题
容易混淆:
- 上图中的各种加载器并非是继承关系。
- 父加载器不是“类加载器的加载器”,也不是“类加载器的父类加载器”。
- 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实现细节
- 字节码层面
ACC_SYNCHRONIZED
monitorenter monitorexit - JVM层面
C C++ 调用了操作系统提供的同步机制 - OS和硬件层面
X86 : lock cmpxchg / xxx
https😕/blog.csdn.net/21aspnet/article/details/
5. happens-before 原则(JVM规定重排序必须遵守的规则)
- 程序的秩序规则:同一个线程内,按照代码出现的顺序,前面的代码先于后面的代码。准确的说是控制流程,因为要考虑到分支循环结构。
- 管程锁定规则:一个unlock 操作先行发生于后面(时间上)对同一个锁的lock操作。
volatile 变量规则:对一个volatile 变量的写操作先行发生于后面(时间上)对这个变量的读操作。 - 线程启动规则:Thread 的start() 方法先行发生于这个线程的每一个操作。
- 线程中断规则:对线程的interrupt() 方法的调用先行发生于被中断线程的代码检测到中断时间的发生,可以通过Thread.interrupt() 方法检测线程是否中断。
- 线程终止规则:线程的所有操作都先行于此线程的终止操作。可以通过Thread.join() 方法结束、Thread.isAlive() 的返回值等手段检测线程的终止。
- 对象终结规则:一个对象的初始化完成先行于发生它的finalize() 方法的开始。
- 传递性:如果操作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. 对象的大小
对象的大小跟虚拟机的实现以及虚拟机的设置都有关系。
普通对象:
- 对象头:markword 8
- ClassPointer指针:-XX:+UseCompressedClassPointers 为4字节 不开启为8字节
- 实例数据
- 引用类型:-XX:+UseCompressedOops 为4字节 不开启为8字节
Oops Ordinary Object Pointers
- 引用类型:-XX:+UseCompressedOops 为4字节 不开启为8字节
- Padding对齐,64位机器按照块读取,8的倍数
数组对象:
- 对象头:markword 8
- ClassPointer指针同上
- 数组长度:4字节
- 数组数据
- 对齐 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. 对象是怎么定位的
- 句柄池;
- 直接指针(Hotspot 使用了这种)
6. 对象怎么分配
GC 相关的内容