class文件生命周期
前4字节魔数
minor version,major version:版本号
constant pool count:常量池,前两个字节常量池长度,
constant pool:后面跟的内容(内容实际长度为定义的长度-1(class预留了一个自己用))
access flags:类的访问权限
this class:2字节记录,指向常量池中类名的引用
superclass:指向父类类名引用
interface count:表示实现了多少接口,
interfaces:后面跟的与之相对应接口的常量池的引用(interfaces)
fields count:属性长度
fields:具体属性指向常量池位置(成员变量存在对应的methods中)
methods count:方法数量
methods:一个个的方法
attribute count:属性
attribute:各种属性,最重要的是code:方法里的代码
加载过程(双亲委派机制)
loading:
将class加载内存
父加载器不是类加载器的加载器,也不是类加载器的父类加载器!!!
类加载器的父加载器关系(双亲委派模型)
父加载器在构造方法中指定
代码:
System.out.println(String.class.getClassLoader());
System.out.println(sun.awt.HKSCS.class.getClassLoader());
System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());
System.out.println(T002_ClassLoaderLevel.class.getClassLoader());
System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getClass().getClassLoader());
System.out.println(T002_ClassLoaderLevel.class.getClassLoader().getClass().getClassLoader());
System.out.println(new T006_MSBClassLoader().getParent());
System.out.println(ClassLoader.getSystemClassLoader());
运行结果:
null
null
sun.misc.Launcher$ExtClassLoader@677327b6
sun.misc.Launcher$AppClassLoader@18b4aac2
null
null
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader@18b4aac2
结果第一行为null:表明用的类加载器为bootstrap,bootstrap类加载器c++实现,java中直接访问是没有类与之对应。因此代码中获取结果为null
第三行为extclassloader:因为该包在jdk的ext目录下
第四行的appclassloader:是自定义的类,类加载器为app
类加载器的加载器关系
bootstrap < —— extclassloader,appclassloader
类加载器的继承关系
查看类加载器的scope
public class T003_ClassLoaderScope {
public static void main(String[] args) {
String pathBoot = System.getProperty("sun.boot.class.path");
System.out.println(pathBoot.replaceAll(";", System.lineSeparator()));
System.out.println("--------------------");
String pathExt = System.getProperty("java.ext.dirs");
System.out.println(pathExt.replaceAll(";", System.lineSeparator()));
System.out.println("--------------------");
String pathApp = System.getProperty("java.class.path");
System.out.println(pathApp.replaceAll(";", System.lineSeparator()));
}
}
双亲委派目的:为了安全!
思考:如果java.lang.string由自定义加载器加载,或自定义String类被appclassloader加载的风险。
举例:如果自定义了String类并打包给客户,客户代码中有获取密码的逻辑是将密码转成string,此时如果不是双亲委派模型,没有自上而下加载过程,那就会加载到app classloader中自定义的String,出现密码泄露风险
parent是如何指定的,如何打破双亲委派、何时打破过双亲委派
1. 用super(parent)指定
2. 双亲委派的打破
1. 如何打破:重写loadClass()
2. 何时打破过?
1. JDK1.2之前,自定义ClassLoader都必须重写loadClass()
2. ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定
3. 热启动,热部署
1. osgi tomcat 都有自己的模块指定classloader(可以加载同一类库的不同版本)
package com.mashibing.jvm.c2_classloader;
import com.mashibing.jvm.Hello;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class T012_ClassReloading2 {
private static class MyLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
File f = new File("C:/work/ijprojects/JVM/out/production/JVM/" + name.replace(".", "/").concat(".class"));
if(!f.exists()) return super.loadClass(name);
try {
InputStream is = new FileInputStream(f);
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
e.printStackTrace();
}
return super.loadClass(name);
}
}
public static void main(String[] args) throws Exception {
MyLoader m = new MyLoader();
Class clazz = m.loadClass("com.mashibing.jvm.Hello");
m = new MyLoader();
Class clazzNew = m.loadClass("com.mashibing.jvm.Hello");
System.out.println(clazz == clazzNew);
}
}
自定义类加载器
1. extends ClassLoader
2. overwrite findClass() -> defineClass(byte[] -> Class clazz)
3. 加密
1.手动加载类
public class T005_LoadClassByHand {
public static void main(String[] args) throws ClassNotFoundException {
Class clazz = T005_LoadClassByHand.class.getClassLoader().loadClass("com.mashibing.jvm.c2_classloader.T002_ClassLoaderLevel");
System.out.println(clazz.getName());
//利用类加载器加载资源,参考坦克图片的加载
//T005_LoadClassByHand.class.getClassLoader().getResourceAsStream("");
}
}
2.什么时候需要自己加载类:
spring动态代理,spring自己创建代理类,并放到内存
热部署
源码:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 自下而上的查找cache,是否已经加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 自下而上的查找cache,是否已经加载
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
}
// 自上而下执行findclass去找这个类(自定义classloader只用重写实现这个方法),
// 如果是自己负责的就返回,找不到返回null,下一级继续找
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;
}
}
// 设计模式:模板方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
ClassLoader的源码
1. findInCache -> parent.loadClass -> findClass()
lazyloading
规定什么时候必须初始化class
–new getstatic putstatic invokestatic指令,访问final变量除外
–java.lang.reflect对类进行反射调用时
–初始化子类的时候,父类首先初始化
–虚拟机启动时,被执行的主类必须初始化
–动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化
verification:
校验,验证文件是否符合JVM规定
preparation:
将class文件静态变量赋值默认值(如int i=8,此处赋值为0)
resolution:
1. 将类、方法、属性等符号引用解析为直接引用
2. 常量池中的各种符号引用(指向class中常量池的位置)解析为指针、偏移量等能访问到的内存地址的直接引用
initializing:
调用类初始化代码 <clinit>,给静态成员变量赋初始值(此处将 i 赋值8)
类加载器 本质
是个class
加载class过程做了两件事
1. 二进制内容被放到内存
2. 生成了一个class对象指向了二进制内容
关于编译器
默认模式是混合模式,就是混合使用解释器加热点代码编译。什么叫热点代码编译?
多次被调用的方法,多次被调用的循环进行编译,怎么检测呢?就是用一个计数器,每个方法上都有一个方法,计数器循环有循环计数器。结果在发现某个方法一秒钟执行了超过某个10万次。我要对他进行编译,拿大腿想一想我该怎么办?直接编译成本地代码,再用的话直接用本地的。不用解释器执行了。
这时候会有人问,为什么不直接都编译成本地代码呢,执行效率更高,因为Java解释器现在效率已经非常高了,在一些简单的代码上它不属于便器。第二点,如果你有一段代码执行文件,特别特别多各种各样的类库,有时候好几十个class,这是正常的。你上来二话不说先给编译器让他编译,编译的过程会长的吓人,所以现在默认的模式是混合模式,但是完全可以用参数来指定到底是什么模式。
编译比解释启动慢,执行快
检测热点代码:-XX:CompileThreshold = 10000