JVM架构介绍
(ps:有颜色的代表线程共享,存在垃圾回收;无颜色代表线程私有,不存在垃圾回收情况。)
类加载器 ClassLoader
1.ClassLoader 介绍
ClassLoader 是一个java执行时的系统组件,主要负责加载Class字节码文件,相当于快递员。
2.ClassLoader 分类
JVM有3个默认的ClassLoader
- 启动类加载器(Bootstrap) 用C++写的
- 扩展类加载器(Extension) 用java写的
- 应用类加载器(AppClassLoader)-- 也称系统类加载器,加载自己写的类。
还有一种是: - 用户自定义加载器
3.ClassLoader的加载原理图:
4.实例
package com.fll.jvm;
public class ClassLoader_Test01 {
/**
* 类加载器ClassLoader分为三类
* 1.启动类加载器 -- Bootstrap c++写的
* 2.扩展类加载器 -- Extension 用java写的
* 3.应用类加载器 -- AppClassLoader: 也称系统类加载器,加载自己写的类。
*/
public static void main(String[] args) {
Object object = new Object();
System.out.println("Object类的getClassLoader(): "+object.getClass().getClassLoader());
/*
* 输出为:Object类的getClassLoader(): null
* 为什么?
* 原因是Object类对象是所有类的祖先类,是JDK自带的类,这些类由Bootstrap启动类加载器加载的,Bootstrap启动类加载器是C++写的, 控制台无法打印。
* rt.jar
*/
// System.out.println("Object类的getClassLoader().getParent(): "+object.getClass().getClassLoader().getParent());
// System.out.println("Object类的getClassLoader().getParent().getParent(): "+object.getClass().getClassLoader().getParent().getParent());
/**
* 输出为:java.lang.NullPointerException
*/
ClassLoader_Test01 test = new ClassLoader_Test01();
System.out.println("test类的getClassLoader(): "+test.getClass().getClassLoader());
/*
* 输出为:test类的getClassLoader(): sun.misc.Launcher$AppClassLoader@2a139a55
* 理解: 自己写的类由AppClassLoader加载
*
* sun.misc.Launcher是什么?
* 是JVM相关调用的入口程序
*
* 那么扩展类加载器是干什么的呢?
* javax开头的包就是用扩展类加载器加载的。
*/
System.out.println("test类的getClassLoader().getParent(): "+test.getClass().getClassLoader().getParent());
System.out.println("test类的getClassLoader().getParent().getParent(): "+test.getClass().getClassLoader().getParent().getParent());
/**
* 输出为:test类的getClassLoader().getParent(): sun.misc.Launcher$ExtClassLoader@7852e922
* test类的getClassLoader().getParent().getParent(): null
* 解释:也就说明ClassLoader的加载顺序是 Bootstrap -> Extension -> AppClassLoader
*/
}
}
5.双亲委派机制
- 双亲委派机制是什么?
当一个类收到了类加载的请求,它首先不会尝试自己去加载这个类,而是把这个类委派给自己的父类去完成,每个层次的类加载器都是如此,最终汇总到启动类加载器(Bootstrap),只有当上层加载器无法加载这个类的时候,才尝试往下层类加载器中找。
- 双亲委派机制的好处是:保证了程序的健壮性
- 实例
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("双亲委派机制测试......");
}
/*输出为:
* 错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
* public static void main(String[] args)
* 否则 JavaFX 应用程序类必须扩展javafx.application.Application
*/
}
- 总结:
三个字:往上找
方法区 Method Area
方法区:供各线程共享的运行时内存区域。它存储了每一个类的结构信息,例如运行时常量池(Runtime Constant Pool)
、字段
、方法数据
、构造函数
和普通方法
的字节码内容。
在不同虚拟机里实现不一样,最典型的是1.7的永久代(PermGen Space)和1.8的元空间(Meta Space)。
栈 Stack ☆☆☆
栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命周期跟随线程的生命周期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,是线程私有
的。
8种基本来兴的变量+对象的引用变量+实例方法都是在函数的占内存中进行分配
栈有个特点:后进先出
栈存储什么?
栈帧中主要保存3类数据:
本地变量:输入参数和输出参数以及方法内的变量;
栈操作:记录出栈,入栈的操作;
栈帧数据:包括类文件,方法等。
堆 Heap ☆☆☆
Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。
Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。
此内存区域的唯一目的
就是存放对象实例, Java 世界里“几乎”所有的对象实例都在这里分配内存。
Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩
展来实现的(通过参数-Xmx和-Xms设定)。如果在Java堆中没有内存完成实例分配,并且堆也无法再
扩展时, Java虚拟机将会抛出OutOfMemoryError异常。
程序计数器 Program Counter Register
程序计数器是一块较小的内存空间,他的作用可以看做是当前线程所执行的字节码的行号指示器。
字节码解释器工作时就是通过将此计数器的地址指向下一条要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等操作都依赖这个计数器来完成。
本地方法栈 Native Method Stack
本地方法栈主要是为虚拟机使用到的Native方法服务。
native关键字修饰的方法就是:调用的其他语言(例如C)的方法。