JVM_2 class文件

307 阅读6分钟

ClassFileFormat

  • 二进制字节流
  • 数据类型:u1 u2 u4 u8 和 _info(表类型)     1. _info的来源是hotspot源码中的写法
  • 查看16进制格式的ClassFile     1. sublime/notepad

    2. IDEA插件-BinEd

1628500854.jpg

IDEA中使用,File->Open As Binary->找到对应的class文件并打开

  1. 有很多可以观察ByteCode的方法:
  2. javap

javap -v F:\jvm\c1_bytecode\T0100_ByteCode01.class【查询java汇编代码】

  1. JBE-可以直接修改
  2. IDEA插件-JClasslib      IDEA中使用,鼠标光标放到对应文件中,View->Show ByteCode With Jclasslib打开
  • ClassFile构成

BinEd转化的16进制格式的class文件 1628557469.jpg CAFE BABE:表明文件的格式,是java编译成的class文件【Magic Number】

CLASS文件结构__Java1.8_马老师.xmind【提取码:mmgc】

class_loading_linking_initialing【类加载-初始化】

1628558026.jpg

  1. 加载:在硬盘上查找并通过IO读入字节码文件,使用类时才会加载,例如调用类的main()方法,new对象等,在加载对象阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。 在加载阶段会在内存中存在两份文件:一份是原二进制字节码文件,另一份是类.class对象文件

  2. 链接:

  • 验证(Verification):检验字节码文件的正确性
  • 准备(Preparation):给类的静态变量分配内存,并赋予默认值
  • 解析(Resolution):将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态连接是在程序运行期间完成的将符号引用替换为直接引用。【将类、方法、属性等符号引用解析为直接引用,常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用】
  1. 初始化:对类的静态变量初始化为指定值,执行静态代码块

类加载器

1628558211.jpg

Bootstrap类加载器是由c++实现的,在java中没有一个类与之对应,所以打印出来是null

各个类加载器之间没有java语法上的继承关系

为什么要使用双亲委派机制? 

  1. 安全考虑:防止核心类被篡改(主要)
  2. 避免类被重复加载(次要)

类加载器范围

启动器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源码解析

  1. loadClass

1628567630.jpg

  1. findClass

如果是AppClassLoader首先会执行URLClassLoader的findClass方法

1628577700.jpg

自定义类加载器,代码演示

将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默认是使用混合模式

  • 解释器
    1. bytecode intepreter
  • JIT
    1. 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)中

何时应该打破?

  1. JDK 1.2之前,自定义ClassLoader都必须重写loadClass方法
  2. ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定
  3. 热启动、热部署
    • 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);
    }
}

热部署的思想是删除之前所有本地加载的文件,重新生成