1.JDK-JRE-JVM
xxx.java 经过编译生成一个class字节码文件,class文件要在JVM虚拟机上才能运行。
JVM是用来运行.class字节码文件的;java要想在操作系统运行除了jvm还要有核心类库,核心类库就在jre上面;JDK是java开发工具,包含jre和jvm;
C:\Users\13267>java -version
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)
java -version查看安装的jdk版本等信息。java是跨平台的,编译好的class文件可以在windows,Linux操作系统运行。mixed mode,解释执行和编译执行混合的,代码用到的次数特别多的就会编译成一个本地的编译。
xxx.java文件用javac命令生成xxx.class字节码文件,classLoader类装载和java类库解释和混合编译通过字节码解释器和JIT即时编译器到执行引擎,然后到OS硬件。
java语言是跨平台的语言,平台无关性;JVM是跨语言的平台,只要编译成字节码符合JVM规范都可以执行。
2.class字节码文件-classFileFormat
class文件是一个二进制的字节流,是由java虚拟机来解释的
java文件,只有一个类名
class字节码文件,含有一个无参构造,默认会添加一个无参构造方法;
显示的二进制字节码文件,是01组成
16进制打开的形式。CA FE BA BE开头格式,16进制转为10进制可以查看下面编码代表什么,0000表示0是Minor Version,0034是52表示是jdk1.8版本,0010是16表示常量池有16-1=15个存储数据,constant_pool_count-1 个常量 从1开始 16-1 是15常量 有一个0存在 有引用指向表示不指向任何一个常量池任何一项。
javap -v T0100_ByteCode01.class 观察ByteCode命令 java自带的
idea插件jclasslib 插件 ,Constat Pool常量池是class文件最复杂的一个东西,这个文件包含了jdk版本,这个类名,父类名,类权限,属性,字段,方法,构造方法等信息
3.Class Cycle java类的生命周期
Class Loading Linking initializing
-
Verification
1. 验证文件是否符合JVM规定
2. Preparation
1. 静态成员变量赋默认值
3. Resolution
1. 将类、方法、属性等符号引用解析为直接引用
常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
2 Initializing- 调用类初始化代码 ,给静态成员变量赋初始值
GC 是垃圾回收,表示结束了。GC 是垃圾回收,表示结束了。静态变量是在类加载的时候执行的,所以静态Static修饰的,加载一次就初始化一次,所以是单例模式的。
- 调用类初始化代码 ,给静态成员变量赋初始值
/*
System.out.println(T.count);T被classloader装入内存 然后静态变量赋默认值
默认值 count=0 t=null
然后 initializing 初始值
初始值 count=2 t=new T(); new 执行构造函数 count++
T.count=3
public static int count = 2; //0
public static T t = new T(); // null
//private int m = 8;
private T() {
count ++;
//System.out.println("--" + count);
}*/
/*
System.out.println(T.count);T被classloader装入内存 然后静态变量赋默认值
默认值 count=0 t=null
然后 initializing 初始值
new 执行构造函数
t = new T(); count++ 是1 然后count赋值2 count=2
public static T t = new T(); // null
public static int count = 2; //0
//private int m = 8;
private T() {
count ++;
//System.out.println("--" + count);
}*/
4.类加载器
自下向上分别是加载器的父类加载器关系,每个加载器都有自己的内存。为了安全机制所以有双亲委派 防止自己定义java有的类 在加载的时候代替JDK提供的类。
不同的类处于不同的位置在jdk里,有不同的类加载器进行装载,jvm按需动态加载,采用双亲委派机制。
Bootstrap:加载lib/rt.jar charset.jar等核心类,C++实现在java找不到对应的class,在核心类库的class返回的类加载器都是null;
ExtensionClassLoader加载路径java.ext.dirs;App加载classpath路径下的class。这个路径是自己写的类所在位置;自定义类加载器继承classloader重写findclass方法。
class文件平时在硬盘,被load到内存之后,会创建两块内容,第一是把class文件扔到内存,第二是创建一个class类的对象指向这个第一块内容
如何自定义类加载器:
classloader源码:
自定义类加载器就是重写findclass方法:让原本抛出异常的方法正常返回class
5.双亲委派
双亲委派就是孩子向父亲方向的类加载器的内存寻找是否被加载,找到就返回,找不到就继续向上找直到到达顶点,然后父亲向孩子节点寻找class并加载,直到结尾都找不到说明就没有这个class,就会抛出异常:classNotFoundException这个异常。
jvm加载class是懒加载,需要才加载。
为什么要有双亲委派?为了安全机制所以有双亲委派 防止自己定义java有的类 在加载;
1.parent是如何指定的?用super(parent)指定;
2.如何打破双亲委派?重写loadClass()方法
3.什么时候打破双亲委派?
-
JDK1.2之前,自定义ClassLoader都必须重写loadClass()
-
ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定
-
热启动,热部署
- osgi tomcat 都有自己的模块指定classloader(可以加载同一类库的不同版本)
6.jvm是解释和编译混合的模式
修改启动时的vm参数即可指定是解释还是编译
解释:启动快,执行慢;
编译:启动慢,执行快;
因为编译是把经常用的代码提前编译成文件存于本地,调用时直接调用
7.懒加载
- osgi tomcat 都有自己的模块指定classloader(可以加载同一类库的不同版本)
-
LazyLoading 五种情况
1. –new getstatic putstatic invokestatic指令,访问final变量除外 –java.lang.reflect对类进行反射调用时 –初始化子类的时候,父类首先初始化 –虚拟机启动时,被执行的主类必须初始化 –动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化
因为p是x父类,所以先执行加载p静态资源然后加载X,
输出:
P
X
8
9
1.静态类型:
先load内存,赋默认值,然后调用构造函数赋予初始值
2.对象:
成员变量 new object
1分配空间申请内存 赋默认值
2然后调用构造方法 赋初始值
3内存地址赋予变量