一、JVM架构
二、字节码文件
2.1前端编译器
前端编译器的主要任务就是负责将符合Java语法规范的Java代码转换为符合JVM探范的字节码文件。javac编译器在将Java源码编译为一个有效的字节码文件过程中经历了4个步骤,分别是词法解析、语法解析、语义解析以及生成字节码。
2.1.1 javac(IDEA默认使用的)
javac是一种能够将Java源码编译为字节码的前端编译器
2.1.2 ECJ编译器
在Java的前端编译器领域,除了javac之外,还有一种被大家经常用到的前端编译器,那就是内置在Eclipse中的EC(Eclipse Compiler for Java)编译器。和Javac的全量式编译不同,ECJ是一种增量式编译器。
2.3Class的对象
(1) class:
外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
( 2) interface:接口
(3)[]:数组
(4) enum:枚举
(5) annotation:注解@interface
(6) primitive type:基本数据类型
( 7) void
i++的过程
bipush 10 将10放入操作数栈
istore_1 将栈中值放入局部变量表下标为1的位置
iload_1 将局部变量表中的值放入栈中
iinc 1 by 1 将局部变量表中的值加1
istore_1 将栈中的值放入局部变量表中
static void test2(){
Integer a = 128;
Integer b = 128;
log.info("输出的结果为{}",a == b);//false -128~127都会存入缓存中
}
public void test3(){
Integer a = 4;
int b = 4;
log.info("是否相等{}",a == b );//true Integer会自动进行拆箱(能拆不装)
}
2.4class文件的组成部分(16进制)
进制在线转换
https://www.sojson.com/hexconvert.html
魔数
Class文件版本
常量池
访问标识(或标志)
类索引,父类索引,接口索引集合
字段表集合
方法表集合
属性表集合
常量池
常量池:可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型(后面的很多数据类型都会指向此处),也是占用Class文件空间最大的数据项目之一。
常量池表项中,用于存放编译时期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
字面量:1.文本字符串 2.声明为final的常量值
符号引用: 1.类和接口的全限定名 2.字段的名称和描述符 3.方法的名称和描述符
常量类型和结构
常量池中每一项常量都是一个表,JDK1.7之后共有14种不同的表结构数据。如下表格所示:
字节码指令
https://blog.csdn.net/qq_33521184/article/details/105622903?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163939550716780274184839%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=163939550716780274184839&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-105622903.first_rank_v2_pc_rank_v29&utm_term=java%E5%AD%97%E8%8A%82%E7%A0%81%E6%8C%87%E4%BB%A4&spm=1018.2226.3001.4187
java数据类型
三、类加载器
3.1类的加载过程
3.1.1 Loading(装载)阶段
将Java类的字节码文件加载到机器内存中,并在内存中构建出Java类的原型——类模板对象。
步骤:
1.通过类的全名,获取类的二进制数据流。
2.解析类的二进制数据流为方法区内的数据结构(Java类模型)
3.创建java.lang.Class类的实例,表示该类型。作为方法区这个类的各种数据的访问入口
类模型的位置
加载的类在JVM中创建相应的类结构,类结构会存储在方法区(JDK1.8之前:永久代;JDK1.8及之后元空间)。
Class实例的位置
类将.class文件加载至元空间后,会在堆中创建一个Java.lang.class对象,用来封装类位于方法区内的数据结构,该Class对象是在加载类的过程中创建的,每个类都对应有一个class类型的对象。
3.1.2 Linking(连接)阶段 ①验证
保证加载的字节码是合法、合理并符合规范的
②准备
为类的静态变量分配内存,并将其初始化为默认值。
需要注意的点:
1.这里不包含基本数据类型的字段用static final修饰的情况,因为final在编译的时候就会分配了,准备阶段会显式赋值。
2.注意这里不会为实例变量分配初始化,实例变量是会随着对象一起分配到Java堆中。
3.在这个阶段并不会像初始化阶段中那样会有初始化或者代码被执行。
③解析
将符号引用转为直接引用,也就是得到类、字段、方法在内存中的指针或"偏移量。如果直接引用存在,那么可以肯定系统中存在该类、方法或者段。但只存在夺号引用,不能确定系统中一定存在该结构。
3.1.3 Initialization(初始化)阶段
为类的静态变量赋予正确的初始值,以及加载静态代码块。执行类的初始化方法:<clinit>()方法
<clinit>():只有在给类的中的static的变量显式赋值或在静态代码块中赋值了。才会生成此方法。
<init>():一定会出现在Class的method表中。
不会有()的类
public class Test {
//场景1:对于非静态的字段,不管是否进行了显式赋值,都不会生成<clinit>()方法
public int num = 1;
//场景2:静态的字段,没有显式的赋值,不会生成<clinit>()方法
public static int num1;
//场景3:比如对于声明为static final的基本数据类型的字段,不管是否进行了显式赋值,都不会生成<clinit>()方法
public static final int num2 = 1;
}
()产生死锁的问题
class StaticA {
static {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
try {
Class.forName("com.atguigu.java.StaticB");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("StaticA init OK");
}
}
class StaticB {
static {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
try {
Class.forName("com.atguigu.java.StaticA");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("StaticB init OK");
}
}
public class StaticDeadLockMain extends Thread {
private char flag;
public StaticDeadLockMain(char flag) {
this.flag = flag;
this.setName("Thread" + flag);
}
@Override
public void run() {
try {
Class.forName("com.atguigu.java.Static" + flag);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(getName() + " over");
}
public static void main(String[] args) throws InterruptedException {
StaticDeadLockMain loadA = new StaticDeadLockMain('A');
loadA.start();
StaticDeadLockMain loadB = new StaticDeadLockMain('B');
loadB.start();
}
}
3.2类的加载器
类的加载器只在loading阶段,只能影响到类加载的第一个阶段
显式加载:指的是在代码中通过调用ClassLoader加载class对象,如直接使用class.forName(name)或this.getClass().getClassLoader( ).loadClass()加载class对象。
隐式加载:则是不直接在代码中调用ClassLoader的方法加载class对象,而是通过虚拟机自动加载到内存中,如在加载某个类的class文件时,该类的class文件中引用了另外一个类的对象,此时额外引用的类将通过JVM自动加载到内存中。
3.2.1启动类加载器(引导类加载器)
1.这个类加载使用C/C++语言实现的,嵌套在VM内部。
2.它用来加载]ava的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容)。用于提供VM自身需要的类。
3.并不继承自java.lang.ClassLoader,没有父加载器。
4.出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类
5.加载扩展类和应用程序类加载器,并指定为他们的父类加载器。
使用以下参数配置会打印被加载的类()
-XX:+TraceClassLoading
3.2.2拓展类加载器
1.Java语言编写,由sun.misc.Launcher$ExtClassLoader实现。继承于classLoader类
2.父类加载器为启动类加载器
3.从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。
3.2.3系统类加载器
1.java语言编写,由sun.misc.Launcher$AppClassLoader实现继承于ClassLoader类
2.父类加载器为扩展类加载器
3.它负责加载环境变量classpath或系统属性 java.class.path指定路径下的类库应用程序中的类加载器默认是系统类加载器。
4.它是用户自定义类加载器的默认父加载器
5.通过classLoader的getSystemClassLoader()方法可以获取到该类加载器
3.2.4自定义类加载器
1.通过类加载器可以实现非常绝妙的插件机制,如Eclipse的插件机制。类加载器为应用程序提供了一种动态增加新功能的机制,这种机制无须重新打包发布应用程序就能实现。
2.自定义加载器能够实现应用隔离,例如Tomcat,Spring等中间件和组件框架都在内部实现了自定义的加载器
3.所有用户自定义类加载器通常需要继承于抽象类java.lang.classLoader。
3.3ClassLoader源码
ClassLoader
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
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {//是否需要解析,默认为false
resolveClass(c);
}
return c;
}
}
保护机制,即使我们重写了classLoader破坏双亲委派机制,引导类加载器也会加载java下的类
/* Determine protection domain, and check that:
- not define java.* class,
- signer of this class matches signers for the rest of the classes in
package.
*/
private ProtectionDomain preDefineClass(String name,
ProtectionDomain pd)
{
if (!checkName(name))
throw new NoClassDefFoundError("IllegalName: " + name);
// Note: Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
// relies on the fact that spoofing is impossible if a class has a name
// of the form "java.*"
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;
}
3.5自定义类加载器
3.5.1自定义类加载器的用途
1.隔离加载类
2.修改类加载的方式
3.扩展加载源
4.防止源码泄漏