JVM与类加载器记录
JVM执行JAVA类与反编译CLASS文件一、JVM执行过程1.jvm概念2.类加载3.内存区域二、反编译1.javap2.反编译工具一、JVM执行过程1.jvm概念Java Virtual Machine(Java虚拟机),JVM有自己完善的架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关从而实现跨平台,Java的
JVM内存,类加载过程与反编译CLASS文件
一、JVM执行
1.jvm概念
Java Virtual Machine(Java虚拟机),JVM有处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关从而实现跨平台,Java的程序编译后的.Class文件,JVM识别并运行它,针对每个操作系统开发其对应的解释器,只要操作系统有对应版本的JVM,那么编译后的代码就能够运行起来,这就是 一次编译,到处运行
流程:
Java源文件 > 编译器 > 字节码文件 > JVM > 机器码
2.类加载
2.1 加载体系
BootStrapClassLoader > ExtClassLoader > AppClassLoader
加载目录
BootStrap
: 启动类加载器,sun.boot.class.path,jre/lib下
Extention
: 扩展类加载器java.ext.dirs,jre/lib/ext
AppClassLoader
: 系统应用类加载器java.class.path
UserClassLoader
: 通过继承 java.lang.ClassLoader等父类加载器,覆盖findClass方法,调用defineClass方法定义类
双亲委派机制
向上级委托查找,向下级委托加载,类加载器加载过的类都有一个 缓存
-
目的:保证使用不同的类加载器最终得到的都是
同一个 Object 对象
-
打破机制:让自定义类加载器
直接加载
所需类,而不是从委托父级去加载 -
重写loadClass:会有沙箱安全机制保护核心类,防止打破双亲委派机制
防篡改
,包名加类名都重复的话就报异常// 自定义加载目录 if (name.startsWith("cn.xxx")){ c = findClass(name); } else { c = this.getParent().loadClass(name); }
// 多版本共存 if (c == null){ c = findClass(name); if (c == null) { c = super.loadClass(name, resolve); } }
-
SPI机制:多版本加载,例如:JDBC
热加载器
- 原因:缓存后导致无法进行类的热加载
- 实现:通过new类加载器进行热加载
- 问题:加载过程中容易出错,必然会导致大量垃圾对象的产生
- resolve:loadClass第二个参数,控制所加载的类是否被链接,默认false,将编译阶段检测到的错误延迟到了运行时
加载途径
- 网络路径,maven仓库
- 本地目录直接加载
- 源文件动态编译
2.2 加载过程
JVM 类加载机制分为五个部分:加载
,验证
,准备
,解析
,初始化
加载
- 查找并加载类的
二进制
数据(查找和导入Class文件) - 通过一个类的
全限定名
来获取其定义的二进制字节流 - 将这个字节流所代表的静态存储结构转化为方法区的
运行时数据结构
- 在Java堆中生成一个代表这个类的java.lang.
Class
对象,作为对方法区中这些数据的访问入口,不一定非得要从一个 Class 文件获取,也可以从ZIP包
中读取(jar包、war包中读取),也可运行时计算生成(动态代理
),也可以由其它文件生成
链接:验证、准备、解析
- 验证:字节信息是否符合
jvm规范
,确保被加载的类的正确性,防止黑客伪造jvm,保证jvm安全 - 准备:为类的静态变量分配内存,并将其初始化为默认值,主要
预分配
内存- 进行内存分配的仅包括类变量,不包括实例变量,实例变量在对象实例化时才随着对象分配在Java堆中
- 这里设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在代码中被显式赋予的值
- 解析:把类中的符号引用转换为
直接引用
,将代码转为计算机可以理解的堆栈引用,内存中的映射
初始化
- 对类的静态变量,静态代码块执行初始化操作
- 执行静态代码块,为类变量指定初始值
类的初始化时机
- new一个对象
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射(Class.forName(“cn.xxx”))
- 初始化一个类的子类(会首先初始化子类的父类)
- JVM启动时标明的启动类,即文件名和类名相同的那个类 只有这6中情况才会导致类的类的初始化
不会执行初始化
- 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化
- 定义对象数组,不会触发该类的初始化
- 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触
发定义常量所在的类 - 通过类名获取 Class 对象,不会触发类的初始化
- 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初
始化,其实这个参数是告诉虚拟机,是否要对类进行初始化 - 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作
类加载的三种方式
- 通过
命令行
启动应用时由JVM初始化加载含有main()方法的主类 - 通过
Class.forName()
方法动态加载,会默认执行初始化块(static{}),但是Class.forName(name,initialize,loader)中的initialze可指定是否要执行初始化块 - 通过
ClassLoader.loadClass()
方法动态加载,不会执行初始化块
3.内存区域
3.1 程序计数器(线程私有)
- 记录正在执行或即将执行代码行号位置,从程序计数器中挖去,真实值内存地址
- 多线程执行时,CPU切换线程恢复执行要使用到程序计数器
- 字节码执行引擎用来修改计数器
- 较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的
程序计数器 - 执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如
果还是 Native 方法,则为空 - 该区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域
3.2 栈(线程私有)
-
每个方法在执行都会创建一个栈帧(Stack Frame),用于存储
局部变量表
、操作数栈
、动态链接
、方法出口
等信息- 局部变量:istore_1(将值存入局部变量,操作数栈出栈),对象引用为堆中的地址
- 操作数栈:iconst_1(将常量压入操作数栈)
- 动态链接:方法调用,将符号引用转为直接引用,从方法区寻找地址来执行
- 方法出口:将返回信息存储在此,根据存储信息继续向下执行
-
方法从调用直至执行完成的过程对应着一个栈帧在虚拟机栈中入栈到出栈的过程,先进后出特性
-
栈帧是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接
(Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception) -
栈帧随着方法调用而创建,随着方法结束而销毁,无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束
3.3 本地方法区(线程私有)
- Native 方法,底层不是JAVA代码
- 虚拟机栈为执行 Java 方法服务, 而本地方法栈则为Native方法服务
- 如果一个 VM 实现使用 C-linkage 模型来支持Native调用, 那么该栈将会是一个
C 栈,但 HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一
3.4 堆(线程共享)
- 被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行
垃圾收集的最重要的内存区域 - GC 的角度细分为: 新生代(Eden 区、From Survivor 区和 To Survivor 区,比例8:1:1)和老年代,比例1:2
3.5 方法区/元空间(线程共享)
- 与堆关系,静态变量为对象类型,亦指向堆中的内存地址
- 存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
- 运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版
本、字段、方法、接口等描述等信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中
二、反编译
1.javap
用法: javap <options> <classes>
其中, 可能的选项包括:
-help --help -? 输出此用法消息
-version 版本信息
-v -verbose 输出附加信息
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类
和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的
系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示最终常量
-classpath <path> 指定查找用户类文件的位置
-cp <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置
反编译至文本文件
javap -p -c Goods.class > Test.txt
部分内容如下
public boolean equals(java.lang.Object);
Code:
0: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #10 // String 调用了equals方法
5: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: aload_1
9: instanceof #12 // class lessonTest7/Goods
12: ifne 17
15: iconst_0
16: ireturn
17: aload_1
18: checkcast #12 // class lessonTest7/Goods
21: astore_2
22: aload_2
23: getfield #2 // Field name:Ljava/lang/String;
26: aload_0
27: getfield #2 // Field name:Ljava/lang/String;
30: invokevirtual #13 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
33: ifeq 52
36: aload_2
37: getfield #3 // Field category:Ljava/lang/String;
40: aload_0
41: getfield #3 // Field category:Ljava/lang/String;
44: invokevirtual #13 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
47: ifeq 52
50: iconst_1
51: ireturn
52: iconst_0
53: ireturn
public int hashCode();
Code:
0: aload_0
1: getfield #2 // Field name:Ljava/lang/String;
4: invokevirtual #14 // Method java/lang/String.hashCode:()I
7: ireturn
执行手册:jvm指令手册跳转
2.反编译工具
jd-gui
下载地址:http://java-decompiler.github.io/
其实还有很多很多的反编译工具,百度一下你就知道哦
更多推荐
所有评论(0)