JVM虚拟机——java类加载机制
那么java类是如何被加载到jvm虚拟机的呢?
一、JAVA8的类加载机制
三句话总结JDK8的类加载机制:
类缓存:每个类加载器对加载过的类都有一个缓存。
双亲委派:向上委托查找,向下委托加载。
(向上委托查找是从缓存中查,为了保证系统内置的类不能够被子类覆盖,比如说Java.long.object,这是所有子类分父类。 为了保证父类不被修改)
3.沙箱保护机制:不允许应用程序加载JDK内部的系统类。(不能写java.开头的类)
至于JDK具体如何执行的,不同JDK版本的实现方式是不同的。以下以大家最为熟悉的JDK8进行分析。
2、双亲委派机制
JDK8中的两个类加载体系:

JDK8中的类加载器都继承于一个统一的抽象类ClassLoader,类加载的核心也在这个父类中。其中,加载类的核心方法如下:
//类加载器的核心方法 public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } 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 PerfCounter.getParentDelegationTime().addTime(t1 - t0); PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }这个方法里,就是最为核心的双亲委派机制。虽然从JDK8往后,类加载机制有了很多的调整,但是这段双亲委派的经典代码却没有发生变化。
这里有趣的是,这个loadClass方法是用protected声明的。这意味着,是可以被子类覆盖的。所以,双亲委派机制也是可以被打破的。
当一个类加载器要加载一个类时,整体的过程就是通过双亲委派机制向上委托查找,如果没有查找到,就向下委托加载。整个过程整理如下图:
双亲委派机制过程:将某个class类加载到缓存,这个类加载器会先在自己的缓存中找,看有没有加载过,加载过就返回,如果没有加载就到父加载器的缓存中找,如果有返回,没有就到顶层的加载器BootStapClassLoade中找,去缓存里找如果有返回,如果没有就到对应的目录文件中有没有class文件,有加载,没有到下面的子加载器再找。直到上面都没有,就交由自己加载。

另外,还一个有趣的地方,这个loadClass方法中有一个resolve参数,但是,设置这个参数,却只有一个public方法中写死了。
也就是说,在调用类加载器时,程序员是没有办法给这个resolve方法主动传入一个false的。那这个resolve参数设置不是多此一举吗?

其中关于半初始化状态(准备阶段)就是JDK在处理一个类的static静态属性时,会先给这个属性分配一个默认值,作用是占住内存。然后等连接过程完成后,在后面的初始化阶段,再将静态属性从默认值修改为指定的初始值。
例如参照一下下面这个案例:
class Apple{
static Apple apple = new Apple(10);
static double price = 20.00;
double totalpay;
public Apple (double discount) {
System.out.println("===="+price);
totalpay = price - discount;
}
}
public class PriceTest01 {
public static void main(String[] args) {
System.out.println(Apple.apple.totalpay);
}
}其中Apple.apple访问了类的静态变量,会触发类的初始化,即加载-》链接-》初始化
而当main方法执行构造函数时,price还没有初始化完成,处于链接阶段的准备阶段,其值为默认值0。这时构造函数的price就是0,所以最终打印出来的结果是-10, 而不是 10 。
2在代码前加final ,也就是 final static double price = 20.00。表示这个值永远不会变,所以开始直接就是初始值。
如果A类中有一个静态属性,引用了另一个B类。那么在对类进行初始化的过程中,因为A和B这两个类都没有初始化,JVM并不知道A和B这两个类的具体地址。所以这时,在A类中,只能创建一个不知道具体地址的引用,指向B类。这个引用就称为符号引用。而当A类和B类都完成初始化后,JVM自然就需要将这个符号引用转而指向B类具体的内存地址,这个引用就称为直接引用。

3、沙箱保护机制
private ProtectionDomain preDefineClass(String name, ProtectionDomain pd)
{
if (!checkName(name))
throw new NoClassDefFoundError("IllegalName: " + name);
// 不允许加载核心类
if ((name != null) && name.startsWith("java.")) {
throw new SecurityException
("Prohibited package name: " +
name.substring(0, name.lastIndexOf('.')));
}
if (pd == null) {
pd = defaultDomain;
}
if (name != null) checkCerts(name, pd.getCodeSource());
return pd;
}如何对class文件加密,防止反编译?

新的myclass文件如下:


4、类和对象有什么关系
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.17</version> </dependency>
然后可以用以下方法简单查看一下对象的内存信息。
public class JOLDemo {
private String id;
private String name;
public static void main(String[] args) {
JOLDemo o = new JOLDemo();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
}
本文原创,转载必追究版权。