# 1、Class 加载过程

## JVM的模式
- 解释执行
- 编译执行
- 混合模式(某个方法重复运行count次,JVM就会把这个方法先编译,下次运行提高效率)
## Class的格式(16进制)
[参考这篇文章](https://www.cnblogs.com/zsql/p/12907120.html)
## 具体加载过程
通过类加载器把字节码加载到JVM内存中,生成Class对象,并放入缓存中。
- Loading 加载 :二进制码加载到内存
- Linking 连接
- 验证 :验证字节码是否符合JVM规范
- 准备 :静态成员变量赋**默认值**,例如int默认值为0.
- 解析 :将字节码信息中的method,interface解析为直接引用
- Initialing 初始化:静态成员变量初始化
## 双亲委派机制(Jvm类加载机制)
- 分析源码可知,当前的类加载器会检测parent加载器是否加载过,如果没有自己才会去进行加载。
- 好处:
- 1、系统安全,防止别人篡改系统内部类
- 2、避免重复加载
- loadClass写了双亲委派, 要想打破这个机制需要重写loadClass
## Java类加载器底层源码
### 双亲委派
```java
public abstract class ClassLoader {
// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
private final ClassLoader parent;
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
```
## 自定义类加载器应用
继承classLoader,重写findClass方法
### 热部署: 检测class文件发生变化后重新load
### 加解密
```java
import com.jvm.Hello;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class MyClassLoader extends ClassLoader {
public static int seed = 0B10110110;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
File f = new File("c:/test/", name.replace('.', '/').concat(".myclass"));
try {
FileInputStream fis = new FileInputStream(f);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;
while ((b=fis.read()) !=0) {
baos.write(b ^ seed);
}
byte[] bytes = baos.toByteArray();
baos.close();
fis.close();//可以写的更加严谨
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name); //throws ClassNotFoundException
}
public static void main(String[] args) throws Exception {
encFile("com.jvm.hello");
ClassLoader l = new T007_MSBClassLoaderWithEncription();
Class clazz = l.loadClass("com.jvm.Hello");
Hello h = (Hello)clazz.newInstance();
h.m();
System.out.println(l.getClass().getClassLoader());
System.out.println(l.getParent());
}
private static void encFile(String name) throws Exception {
File f = new File("c:/test/", name.replace('.', '/').concat(".class"));
FileInputStream fis = new FileInputStream(f);
FileOutputStream fos = new FileOutputStream(new File("c:/test/", name.replaceAll(".", "/").concat(".myclass")));
int b = 0;
while((b = fis.read()) != -1) {
fos.write(b ^ seed);
}
fis.close();
fos.close();
}
}
```
# 2、JMM(Java内存模型)
## 缓存一致性
- 大多数CPU通过主线锁和缓存一致性协议完成数据一致性,当数据过大,不适合用缓存,需要通过使用主线锁完成数据一致。
- **Intel**中的CPU中大多使用的是MESI缓存一致性协议
- MESI包括了四种状态,分别是:
- Modift
- Exclusive
- Shared
- Invalid
## 缓存行和伪共享
- **缓存行**:CPU缓存的基本单位,一般为64字节
- 当读取一个数据,数据没有占64字节,而一个缓存行有64位,他会把在这一行上的其他数据也读进来。
- **伪共享**:同一缓存行上的不同数据,被两个CPU锁定,会频繁的产生数据刷新。为了解决伪共享,可以通过缓存行对其提高效率,让两个数据不存在同一个行上。
- 例如:disruptor框架中的指针使用了7个volatile long类型占满一个缓存行。
## 执行顺序
- 乱序执行,可能会产生数据不一致。
- 因为CPU的运行速度是寄存器的100+倍,所以在从寄存器读取数据的时候,CPU处于空闲,那么空闲状态的时候就可以执行其他指令,所以指令是乱序的。
- 不乱序执行,通过**内存屏障**
## 内存屏障与JVM指令
- CPU x86
- sfence:写屏障
- lfence:读屏障
- mfence:读写屏障
- lock
- JVM 依赖于CPU的读写屏障
- LoadLoad
- StoreStore
- LoadStore
- StoreLoad
### volatile实现的指令
- 字节码层面:增加了access_flags = volatile
- JVM层面:
- 写:增加了StoreStoreBarrier 和 StoreLoadBarrier
- 读:增加了LoadLoadBarrier 和 LoadStoreBarrier
- OS和硬件层面:
- windows下使用hisdis查看hotspot反汇编,通过lock指令实现。
### synchronized实现的指令
- 字节码层面:
- 同步方法:增加access_flags = synchronized
- 同步代码块:monitorEntry 和 两个monitorExit,其中一个是异常进行退出。
- JVM层面:C和C++调用了操作系统的同步机制
- OS和硬件层面:lock指令
## 对象内存布局 FAQ
### a、对象的创建过程
- Class loading
- Class Linking 验证,准备(**默认值**),解析
- Class Initialing (**初始化**)
- 申请对象内存
- 成员变量赋**默认值**
- 调用构造方法
- 成员变量**初始化**
- super()调用父级的构造
### b、对象在内存中的存储布局?
- 通过查看虚拟机参数(java -XX:+PrintCommandLineFlags -version),判断对象大小。
- **普通对象**
- 对象头 markword 8字节
- ClassPointer指针 默认8字节,开启压缩为4字节
- 实例数据:引用类型的字节大小(int...),开启压缩为默认的一半,其中oops(ordinary object pointers)例如String 默认为8,开启压缩为4字节。
- Padding对齐,8的倍数
- **数组对象**
- 对象头:同上
- ClassPointer指针:同上
- 数组数据:具体字节
- Padding对齐:同上
- *数组长度:4字节(新增的)
### c、对象头(MarkWord)具体包括什么(jdk1.8)(32位)?

<font color='red'>**重点问题:**</font>
- 为什么GC分代年龄为15岁?因为分代年龄在对象头中占4位,最高是15.
- hashcode重写走自己的重写的计算,不重写通过内存信息生成。
- 当一个对象在偏向锁或重量级锁的时候,hashcode存在哪里?当一个对象产生了hashcode,就无法进入偏向锁,是因为hashcode把偏向锁的占位占了,重量级锁不受影响。
### d、对象(变量)怎么定位?(根据虚拟机来)
- **句柄池**:(存放两个指针,一个class地址,一个内存地址)
- 好处:便于垃圾回收
- **直接指针**:直接指向堆内存,堆内存中记录的classpointer指向class
- 好处:速度快
### e、对象怎么分配?(GC相关,见下文)
# 3、Runtime Data Area

## 结构说明
- Heap 堆
- 存放对象
- JDK1.8后因为方法区永久区改为了元空间,**常量**存放于堆中
- OOM:List中无限添加对象
- Stacks 栈
- 每个方法都有一个栈帧,是私有的,通过返回地址返回到上一个指令
- 栈帧中包括:局部变量表,操作数栈,动态连接(指向常量池的引用),方法的返回地址
- idea 安装jclasslib,查看指令可以看到**压栈**和**出栈**操作。
- 例如:int i=8;i=i++;最终i的结果还是8。
- SOF:死递归,会一直创建栈帧,导致栈溢出。
- Method Area 方法区
- 存储每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。
- 1.7 永久区:存放类信息和String常量,静态变量,FGC永久区不会进行回收。
- 1.8 元空间:存放类和类加载器信息,String常量和静态变量存放在堆,FGC会进行回收,空间大小和物理内存挂钩,也可以指定大小。
- 运行时常量池:类或接口被加载后,对应的运行时常量池被创建,String.intern可以在运行时往常量池添加数据
- Native Method Stacks 本地方法栈(C栈)
- 为JVM虚拟机中的方法提供的空间,也就是C,C++方法的提供的栈空间
- Program Counter 程序计数器
- 线程私有的
- 存放指令的位置,循环从PC中取指令,然后执行
- Direct Memory直接内存:IO可以直接调用系统内存空间,而不是JVM所管理的内存。

JVM详解