- 参考:尚硅谷周阳视频及课件
- 资料提取:https://pan.baidu.com/s/1w-M3S8777iR4oekw7S3crA 提取码:6ea8
注意:垃圾回收算法周阳老师讲的有错误,具体在p19,四大垃圾回收算法为复制算法、标记-整理算法、标记-清除算法、分代收集算法(不是引用计数算法)。这里感谢@9c0bd0ceebfa指出。下文已经更正正确,请放心食用。
JVM 位置
JVM 体系结构概览
注意:
我们平时说的栈是指的 Java 栈,native method stack 里面装的都是 native 方法。见下文↓
类装载器
注意:
- 方法区并不是存放方法的区域,其是存放类的描述信息(模板)的地方。
- Class loader只是负责 class 文件的加载,相当于快递员,这个“快递员”并不是只有一家,Class loader 有多种(启动类加载器、拓展类加载器、应用程序加载器、自定义类加载器)
- 加载之前是“小class”,加载之后就变成了“大Class”,这是安装java.lang.Class模板生成了一个实例。“大Class”就装载在方法区,模板实例化之后就得到n个相同的对象
- JVM并不是通过检查文件后缀是不是
.class
来判断是否需要加载的,而是通过文件开头的特定文件标志(cafe babe)
注意:
- JDK1.8之前,硬件决定执行性能,1.8之后 JVN可以不完全受到硬件的性能制约
- Class loader有多种,可以说三个,也可以说是四个(第四个为自己定义的加载器,继承 ClassLoader),系统自带的三个分别为:
- 启动类加载器(Bootstrap) ,C++所写,涉及JVM的本地实现,获取不到(如:new Object().getClass().getClassLoader() 得到的是 null),加载JDK里的API,如java.* ,java.lang
- 扩展类加载器(Extension) ,Java所写,对应的是 jre\lib\ext*,平台类加载器
- 应用程序类加载器(AppClassLoader)。
我们自己 new 的时候创建的是应用程序类加载器(AppClassLoader)。
import com.gmail.fxding2019.T;
public class Test{
//Test:查看类加载器
public static void main(String[] args) {
Object object = new Object();
//查看是那个“ClassLoader”(快递员把Object加载进来的)
System.out.println(object.getClass().getClassLoader());
//查看Object的加载器的上一层
// error Exception in thread "main" java.lang.NullPointerException(已经是祖先了)
//System.out.println(object.getClass().getClassLoader().getParent());
System.out.println();
Test t = new Test();
System.out.println(t.getClass().getClassLoader().getParent().getParent());
System.out.println(t.getClass().getClassLoader().getParent());
System.out.println(t.getClass().getClassLoader());
}
}
/*
*output:
* null
*
* null
* sun.misc.Launcher$ExtClassLoader@4554617c
* sun.misc.Launcher$AppClassLoader@18b4aac2
* */
注意:
- 如果是JDK自带的类(Object、String、ArrayList等),其使用的加载器是Bootstrap加载器;如果自己写的类,使用的是AppClassLoader加载器;Extension加载器是负责将把java更新的程序包的类加载进行
- 输出中,sun.misc.Leauncher是JVM相关调用的入口程序,具体位置:解压 rt.jar 之后可以看到 java\jre\lib\rt.jar\sun\com\misc\Launcher.class 文件。
- Java加载器个数为3+1。前三个是系统自带的,用户可以定制类的加载方式,通过继承Java. lang. ClassLoader。
双亲委派
编译: 从上往下执行,先从系统类库(JDK)中查找这个类,再往下一致查找classpath,若没有,则会抛出编译异常
执行: 自低向上查找,先从classpath中查找,若没有,则继续从父加载器中查找,直到Bootstrap,若找不到,则会抛出ClassNotFoundException异常 ———————————————— 版权声明:本文为CSDN博主「桂花很香,旭很美」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_40959890/article/details/107183798
为什么 Java 要设计双亲委派机制?(双亲委派的好处)
- 防止类的重复加载(如果calsspath中找到就不会在往上查找)
- 安全(防篡改字节信息)
注意:
- 双亲委派机制:“我爸是李刚,有事找我爹”。
例如:需要用一个A.java这个类,首先去顶部 Bootstrap 根加载器去找,找得到你就用,找不到再下降一层,去 Extension 加载器去找,找得到就用,找不到再将一层,去 AppClassLoader 加载器去找,找得到就用,找不到就会报”CLASS NOT FOUND EXCEPTION“。
//测试加载器的加载顺序
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("hello world!");
}
}
/*
* output:
* 错误: 在类 java.lang.String 中找不到 main 方法
* */
上面代码是为了测试加载器的顺序:首先加载的是 Bootstrap 加载器,由于JVM中有 java.lang.String 这个类,所以会首先加载这个类,而不是自己写的类,而这个类中并无 main 方法,所以会报“在类 java.lang.String 中找不到 main 方法”。
这个问题就涉及到,如果有两个相同的类,那么java到底会用哪一个?如果使用用户自己定义的 java.lang.String,那么别使用这个类的程序会去全部出错,所以,为了保证用户写的源代码不污染 java 出厂自带的源代码,而提供了一种“双亲委派”机制,保证“沙箱安全”。即先找到先使用。
执行引擎
执行引擎负责执行运行时数据区加载的函数,JNI接口包括本地函数库的调用。Java没有C、C++运行快的原因:Java面向虚拟机的指令编程,而不是面向软件的应用编程。
Native Interface 本地接口
Thread类的start方法如下:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
Thread类中竟然有一个只有声明没有实现的方法,并使用native
关键字。用native表示,也此方法是系统级(底层操作系统或第三方C语言)的,而不是语言级的,java并不能对其进行操作。native方法装载在native method stack中。
PC 寄存器
- 注意:native方法不归 java 管,所以计数器是空的
上面图中是亮色的地方有两个特点:
- 所有线程共享(灰色是线程私有)
- 存在垃圾回收(有数据共享,才会存在垃圾回收)
方法区(Method Area)
注意:
- 方法区:绝对不是放方法的地方,它是存储的每一个类的结构信息(比如static)
- 永久代和元空间的解释:方法区是一种规范,类似于接口定义的规范:
List list = new ArrayList();
把这种比喻用到方法区则有:- java 7中:
方法区 f = new 永久代();
- java 8中:
方法去 f = new 元空间();
- java 7中:
栈(Stack)
栈帧的概念 java中的方法被扔进虚拟机的栈空间之后就成为“栈帧”,比如main方法,是程序的入口,被压栈之后就成为栈帧。
注意:
- 栈管运行(入栈、出栈),堆管存储(new 出来的对象等)。
- 栈是线程私有的,不存在垃圾回收。
- 栈的生命周期:随着线程的创建而创建,随着线程的销毁而释放。
- 栈中主要保存什么?
- 本地变量
- 栈操作
- 栈帧数据
public class Test{
public static void m(){
m();
}
public static void main(String[] args) {
System.out.println("111");
//Exception in thread "main" java.lang.StackOverflowError
m();
System.out.println("222");
}
}
/*
*output:
* 111
* Exception in thread "main" java.lang.StackOverflowError
* */
注意:
- StackOverflowError是一个“错误**”,而不是“异常”**。
注意:
- Java 堆中存放的是访问类元数据的地址。
HotSpot:如果没有明确指明,JDK的名字就叫HotSpot
元数据:描述数据的数据(即模板,也就是“大Class”)
上面的关系图的一个实例为下图: