类加载器:加载class文件
运行时数据区域:管理内存
执行引擎:垃圾回收器/即时编译器/解释器
本地接口:java使用native修饰的方法
MAC安装命令: brew install jclasslib-bytecode-viewer
- 主副版本号:主(大版本号 )使用当前的主版本号-44得到真正的jdk版本号
* 例如:主版本号为:52,减完后得到jdk1.8
- 副版本号:为主版本号相同时,区分不同版本的标识
- 版本号的作用:判断当前字节码的版本和运行时jdk是否相兼容
* 例如:报错:<font style="color:#DF2A3F;">字节码文件为jdk8,运行时jdk为6</font>
- 版本号不同的解决方案:
- 
iconst_0 # 现将o放入到操作数栈中,
istore_1 #0从操作数栈弹出,存储到局部变量表中下标为1的位置中
注意:下标为0中存储args数组
iload_1 # 将局部变量中的下标为1的内容复制到栈中
iinc1 by 1 将下标为1的值变为1
istore_1 # #0从操作数栈弹出,存储到局部变量表中下标为1的位置中
return 返回
总结:最后的结果还是为0
0 iconst_0
1 istore_1
2 iconst_0
3 istore_2
4 iconst_0
5 istore_3
6 iinc 1 by 1
9 iload_2
10 iconst_1
11 iadd
12 istore_2
13 iinc 3 by 1
16 return
2. 元信息校验(类必须有父类)
3. 主版本号校验
注意点:如果使用final修饰的话,在准备阶段就是赋值
public class Test {
public static void main(String[] args) {
int i = Test1.i;
System.out.println(i);
}
}
class Test1{
public static int i=0;
static{
System.out.println("init");
}
}
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("Test1");
}
}
class Test1{
static{
System.out.println("init");
}
}
public class Test {
static {
System.out.println("init Test");
}
public static void main(String[] args) throws ClassNotFoundException {
Test1 test = new Test1();
}
}
class Test1{
static{
System.out.println("init Test1");
}
}
class-loader 为空
package cn.varin.Test;
import jdk.nashorn.internal.runtime.ScriptEnvironment;
import javax.script.ScriptEngineManager;
import java.io.IOException;
public class Test {
public static void main(String[] args) {
ClassLoader classLoader = ScriptEnvironment.class.getClassLoader();
System.out.println(classLoader);
}
}
package cn.varin.Test;
import jdk.nashorn.internal.runtime.ScriptEnvironment;
import javax.script.ScriptEngineManager;
import java.io.IOException;
public class Test {
public static void main(String[] args) {
ClassLoader classLoader = Person.class.getClassLoader();
System.out.println(classLoader);
}
}
class Person{
private String name;
private int age;
}
1. 示例1:假设cn.varin.Person对象加载,它会先找Application,如果没有找到加载,继续找Ext,没有再继续找 Boot,找到,返回
2. 示例2:加载cn.varin.Person对象都没有被三个加载器加载过,它会从boot找,是否在路径中,没有的话,继续找Ext,没有的话,继续找Application,找到,返回
问题:如果一个类重复出现在三个类加载器中,谁来加载
答案:启动类加载器加载,根据双亲委派机制,它的优先级最高
问题2:在自己的项目中创建一个java.lang.String类,会被加载吗
答案:不能,会返回启动类加载器加载在rt包中的String类
package cn.varin.Test;
import jdk.nashorn.internal.runtime.ScriptEnvironment;
import javax.script.ScriptEngineManager;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader classLoader = Test.class.getClassLoader();
System.out.println(classLoader);
Class<?> aClass = classLoader.loadClass("java.lang.String");
System.out.println(aClass.getClassLoader());
}
}
# 结果为:
Application
null
当一个类加载器区加载某一个类的时候,会自底向上的查询父类是否加载过, 如果加载过,直接返回, 如果没有加载过,会自顶向下查询路径进行加载。 app 的父为:Ext Ext 的父为:Boot
实现方法:重写findClass方法,这样就不会破坏双亲委派机制
利用上下文类加载器加载类,比如:JDBC和JNDI
Thread.currentThread().getContextClassLoader()
运行时数据区是:jVM在运行Java程序过程中管理的内存区域
程序计数器: 作用一:用于存放下一条指令需要执行的地址 作用二:在多线程执行下,程序计数器可以记录CPU切换前每个线程解释执行到那一条指令 问题:程序计数器会发生内存溢出吗: 答案:不会,因为每个线程只存储了一个固定长度的地址,是不会发生内存溢出的。
例题:
答案:6个
2. 操作数栈:
1. 存放临时数据,例如:常量
3. 栈数据
1. 存储:动态链接,方法出口,异常表
String s1 = new String(“1”); String s2 = “1”; 解释:s1中的字符串是通过new对象创立的,内容会存储在堆中 s2中的字符串是直接创立的,会存储在方法区的字符串常量池中 s1和s2都是存储在栈中,
3. 在jdk8后,直接保存方法区中的数据
两大部分: 线程不共享: 程序计数器:记录当前要执行的字节码指令的地址(不会出现内存溢出) Java虚拟机栈: 本地方法栈: Java虚拟机栈和本地方法栈都是采用栈式存储,用于保存方法调用的基本数据(局部变量,操作数等)(会出现内存溢出) 线程共享: 方法区:主要存储类的元信息,以及常量池(会出现内存溢出) 堆区: 存放创建出来的对象(会出现内存溢出 )
jdk6:
jdk7:
jdk8:
软引用:如果一个对象只有软引用,当程序内存不足时,就会将软引用中的数据进行回收。 常用于:缓存中 如何实现软引用:提供SoftReference类实现 案例分析: 当先A对象属于一个强引用,不会不回收
此时:A对象为一个软引用,可能被回收
核心思想:找到内存中存活的对象 ,把不再存活的对象释放 常见的垃圾回收算法: 标记-清除算法 复制算法 标记-整理算法 分代GC 评价标准: STW(stop the world):停止所有的用户线程的时间 吞吐量(越高效率越好):执行用户代码时间➗(执行用户代码时间+GC使劲) 最大暂停时间:在垃圾回收时,STW时间的最大值
2个阶段:
优点:实现简单,第一阶段将存活的标记为1,第二阶段删除非1的对象 缺点: 碎片化:对象删除后,出现多个很小的可用单元; 分配速度慢
执行过程: 准备2块空间(from和To),只使用from空间, 在垃圾回收阶段,将存活的对象复制到to中 回收结束后,将from和to的名称互换。 优点:吞吐量高,不会产生碎片化 缺点:内存使用效率低(每次只能使用一般的空间)
2个阶段:
优点:内存使用效率高,不会产生碎片化 缺点:整理阶段效率不高
组合: