ClassFileFormat
- 二进制字节流
- 数据类型:u1 u2 u4 u8 和 _info(表类型) 1. _info的来源是hotspot源码中的写法
- 查看16进制格式的ClassFile 1. sublime/notepad
2. IDEA插件-BinEd
IDEA中使用,File->Open As Binary->找到对应的class文件并打开
- 有很多可以观察ByteCode的方法:
- javap
javap -v F:\jvm\c1_bytecode\T0100_ByteCode01.class【查询java汇编代码】
- JBE-可以直接修改
- IDEA插件-JClasslib IDEA中使用,鼠标光标放到对应文件中,View->Show ByteCode With Jclasslib打开
- ClassFile构成
BinEd转化的16进制格式的class文件
CAFE BABE:表明文件的格式,是java编译成的class文件【Magic Number】
CLASS文件结构__Java1.8_马老师.xmind【提取码:mmgc】
class_loading_linking_initialing【类加载-初始化】
-
加载:在硬盘上查找并通过IO读入字节码文件,使用类时才会加载,例如调用类的main()方法,new对象等,在加载对象阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。 在加载阶段会在内存中存在两份文件:一份是原二进制字节码文件,另一份是类.class对象文件
-
链接:
- 验证(Verification):检验字节码文件的正确性
- 准备(Preparation):给类的静态变量分配内存,并赋予默认值
- 解析(Resolution):将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态连接是在程序运行期间完成的将符号引用替换为直接引用。【将类、方法、属性等符号引用解析为直接引用,常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用】
- 初始化:对类的静态变量初始化为指定值,执行静态代码块
类加载器
Bootstrap类加载器是由c++实现的,在java中没有一个类与之对应,所以打印出来是null
各个类加载器之间没有java语法上的继承关系
为什么要使用双亲委派机制?
- 安全考虑:防止核心类被篡改(主要)
- 避免类被重复加载(次要)
类加载器范围
启动器Launcher中有三个内部类以及加载类的路径:
- BootClassPathHolder:【sun.boot.class.path】
- ExtClassLoader:【java.ext.dirs】
- AppClassLoader:【java.class.path】
代码验证:
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()));
}
结果
C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\sunrsasign.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar
C:\Program Files\Java\jdk1.8.0_181\jre\classes
--------------------
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
--------------------
C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar
C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar
F:\msb\资料\JVM\out\production\JVM
D:\Program Files\idea\lib\idea_rt.jar
自定义类加载器
使用设计模式中的模板方法
- 继承ClassLoader
- 重写模板方法findClass 1. 调用defineClass
- 自定义类加载器加载自加密的class - 防止反编译
- 防止篡改
ClassLoader源码解析
- loadClass
- findClass
如果是AppClassLoader首先会执行URLClassLoader的findClass方法
自定义类加载器,代码演示
将Hello.class文件放在 F:\temp\com\mashibing\jvm目录下
package com.mashibing.jvm.c2_classloader;
import com.mashibing.jvm.Hello;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
public class T006_MSBClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
name = name.replaceAll(".", "/").concat(".class");
try {
FileInputStream fis = new FileInputStream("F:/temp/" + name);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;
while ((b=fis.read()) !=0) {
baos.write(b);
}
byte[] bytes = baos.toByteArray();
System.out.println(bytes.length);
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 {
ClassLoader l = new T006_MSBClassLoader();
Class clazz = l.loadClass("com.mashibing.jvm.Hello");
Hello h = (Hello)clazz.newInstance();
h.m();
System.out.println(l.getClass().getClassLoader());
System.out.println(l.getParent());
// System.out.println(getSystemClassLoader());
}
加密
思路:一个int值经过两次异或操作后,值还是它自己。这样第一次异或操作可以封装成加密操作,第二次异或可以封装成解密操作。
代码如下:
package com.mashibing.jvm.c2_classloader;
import com.mashibing.jvm.Hello;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class T007_MSBClassLoaderWithEncription extends ClassLoader {
public static int seed = 0B10110110;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
File f = new File("F:/temp/", name.replace('.', '/').concat(".msbclass"));
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.mashibing.jvm.hello");
ClassLoader l = new T007_MSBClassLoaderWithEncription();
Class clazz = l.loadClass("com.mashibing.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("F:/temp/", name.replace('.', '/').concat(".class"));
FileInputStream fis = new FileInputStream(f);
FileOutputStream fos = new FileOutputStream(new File("F:/temp/", name.replaceAll(".", "/").concat(".msbclass")));
int b = 0;
while((b = fis.read()) != -1) {
fos.write(b ^ seed);
}
fis.close();
fos.close();
}
}
编译器
jdk默认是使用混合模式
- 解释器
- bytecode intepreter
- JIT
- Just In-Time compiler
- 混合模式 - 混合使用解释器 + 热点代码编译
- 起始阶段采用解释执行
- 热点代码检测【-XX:CompileThreshold=10000】
* 多次被调用的方法(方法计数器:监测方法执行频率)
* 多次被调用的循环(循环计数器:检测循环执行频率)
* 多次被调用的循环(循环计数器:检测循环执行频率)
* 进行编译
* -Xmixed 默认为混合模式 开始解释执行,启动速度较快,对热点代码实行检测和编译
* -Xint 使用解释模式,启动很快,执行稍慢
* -Xcomp 使用纯编译模式,执行很快,启动很慢
懒加载 lazyloading
- 严格讲应该叫lazyInitialing
- JVM规范并没有规定何时加载
- 但是严格规定了什么时候必须初始化:
1. new getstatic putstatic invokestatic指令,访问final变量除外
2. java.lang.reflect对类进行反射调用时
3. 初始化子类的时候,父类首先初始化
4. 虚拟机启动时,被执行的主类必须初始化
5. 动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化
打破双亲委派机制
双亲委派的业务逻辑是写在java.lang.ClassLoader的loadClass(String name, boolean resolve)中
何时应该打破?
- JDK 1.2之前,自定义ClassLoader都必须重写loadClass方法
- ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定
- 热启动、热部署
- osgi tomcat 都有自己的模块指定ClassLoader(可以加在同一类库的不同版本)
测试类不会被重复加载代码
public static void main(String[] args) throws Exception {
T006_MSBClassLoader msbClassLoader = new T006_MSBClassLoader();
Class clazz = msbClassLoader.loadClass("com.dandan.jvm.Hello");
msbClassLoader = null;
System.out.println(clazz.hashCode());
msbClassLoader = null;
msbClassLoader = new T006_MSBClassLoader();
Class clazz1 = msbClassLoader.loadClass("com.dandan.jvm.Hello");
System.out.println(clazz1.hashCode());
System.out.println(clazz == clazz1);
}
打破双亲委派代码演示
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);
}
}
热部署的思想是删除之前所有本地加载的文件,重新生成