jvm 2012-11-11
1、____________________________________________________________
1,JVM底层结构
1,JVM指令系统
Java指令也是由 操作码和操作数两部分组成。
操作码为8位二进制数,使得JVM最多有256种指令,目前已使用了160多种操作码。
2,JVM寄存器
pc程序计数器//用于记录程序的执行
optop操作数栈顶指针//下面3个都是用于记录指向Java栈区的指针
frame当前执行环境指针
vars指向当前执行环境中第一个局部变量的指针
3,JVM栈结构
JVM为每一个方法创建一个栈框架,以保存该方法的状态信息。每个栈框架包括以下三类信息:
局部变量//vars寄存器指向该变量表中的第一个局部变量。
执行环境//保存解释器对Java字节码进行解释过程中所需的信息
操作数栈//存储运算所需操作数及运算的结果
4,JVM碎片回收堆
Java类的实例所需的存储空间是在堆上分配的。解释器具体承担为类实例分配,回收空间的工作
5,JVM存储区:常量缓冲池、方法区、堆、Java栈、程序计数器和本地方法栈
常量缓冲池用于存储类名称、方法和字段名称以及串常量。
方法区则用于存储Java方法的字节码。
堆保存的是用new创建的对象和数组
栈中主要保存的是基本类型的变量和对象的引用变量,
具体实现方式在JVM规格中没有明确规定。
这使得Java应用程序的存储布局必须在运行过程中确定,依赖于具体平台的实现方式。
JVM还存在缺陷,但JVM的思想是成功的。
2,JVM内存
1,JVMTI--JVM Tool Interface,但JVMTI提供的能监听的event总觉得还太少
2,代码验证:
Runtime r=Runtime.getRuntime();
r.maxMemory();//默认64M
r.freeMemory();
r.totalMemory();//默认-按需从OS动态获取
3,参数说明
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
- Xmx3550m :设置JVM 最大可用内存为3550M。XMS(Extended Memory Specification)说明书-规格。扩展内存。默认64M
-Xms3550m :设置JVM 初始内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM 重新分配内存。
-Xmn2g :设置年代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小 。持久代一般固定大小为64m,持久代Permanent Generation space
-Xss128k :设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
4,参数-命令行设定
java -cp ClassPath -Xmx512m ClassName,//-cp等效于-classpath
5,内存溢出类型
1 、 java.lang.OutOfMemoryError: PermGen space
存放Class和Meta信息,很多Class或第三方jar。大小超过了jvm默认的大小(4M)报错
GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,
它
2、tomcat下的应用
1,设置内存
$TOMCAT_HOME$/CATALINA.BAT第一行,增加
set JAVA_OPTS=%JAVA_OPTS% -Xms512m -Xmx900m -Duser.timezone=GMT+08
2 、 java.lang.OutOfMemoryError: Java heap space
3,手动设置MaxPermSize大小
修改TOMCAT_HOME/bin/catalina.bat(Linux下为catalina.sh),在Java代码
“echo "Using CATALINA_BASE: $CATALINA_BASE"”上面加入以下行:
set JAVA_OPTS=%JAVA_OPTS% -server -XX:PermSize=128M -XX:MaxPermSize=512m
6,Sun JVM分代垃圾回收器把堆内存分成3块:采用分代的策略。
1)年轻代(Young Gen):年轻代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。年轻代分成1个Eden(伊甸园) Space和2个Suvivor(幸存者) Space(命名为A和B)
当对象在堆创建时,将进入年轻代的Eden Space。
垃圾回收器进行垃圾回收时,扫描Eden Space和A Suvivor Space,如果对象仍然存活,则复制到B Suvivor Space,如果B Suvivor Space已经满,则复制到 Old Gen
扫描A Suvivor Space时,如果对象已经经过了几次的扫描仍然存活,JVM认为其为一个Old对象,则将其移到Old Gen。
扫描完毕后,JVM将Eden Space和A Suvivor Space清空,然后交换A和B的角色(即下次垃圾回收时会扫描Eden Space和BSuvivor Space。
Young Gen垃圾回收时,采用将存活对象复制到到空的Suvivor Space的方式来确保不存在内存碎片,采用空间换时间的方式来加速内存垃圾回收。
2)年老代(Tenured Gen):年老代主要存放JVM认为比较old的对象(经过几次的Young Gen的垃圾回收后仍然存在),内存大小相对会比较大,垃圾回收也相对没有那么频繁(譬如可能几个小时一次)。年老代主要采用压缩的方式来避免内存碎片(将存活对象移动到内存片的一边),当然,有些垃圾回收器(譬如CMS垃圾回收器)出于效率的原因,可能会不进行压缩。
3)持久代(Perm Gen):持久代主要存放类定义、字节码和常量等很少会变更的信息,关于这块的垃圾回收策略可以参考我的另一篇BLOG《Tomcat Context reloadabled 与 OutOfMemory(PermSpace) 》。
3,JVM加载类
1,类装载器
装载类时,总是先让自己的(递归)父类装载器装载
Test.class.getClassLoader().getClass()
sun.misc.Launcher$AppClassLoader
Test.class.getClassLoader().getParent().getClass()
sun.misc.Launcher$ExtClassLoader
ExtClassLoader.getParent()为null
BootstrapLoader是用C++语言写的,不存在java类实体
2,JVM按以下顺序搜索并装载所有需要的类:
1, 系统/平台类: jdk1.6.0_02\jre\lib\rt.jar。
2, 扩展类:$JAVA_HOME\jre\lib\ext\*.jar
3, 用户类: 开发者定义的类,没有使用 java 扩展机制的第三方jar.
3,定位JRE:
1. 自己的目录下有没有JRE目录
2. 父目录下的JRE子目录
3. 查询环境变量$JAVA_HOME目录
4,定位到类
“系统类”,“扩展类”,前两种的路径由JRE指定;
“用户类”的查找顺序
1. -classpath(命令行模式下使用);
2. classpath(在环境变量中设计,当有 –classpath时该路径信息不起作用)
3. 缺省路径”.” 当前目录(当前两步都没有时,才会使用缺省路径)
5,类如何被装载-“动态装载”-为了节省内存,但牺牲了相应速度。
隐式的类装载:A b = new A();最常见
显式的类装载Class.forName("test.A");
类的声明,并不去装载。这一点和编译类是不相同的。
6,类装载器的隔离性
两个位于不同分支的类装载器具有隔离性,它们装载同一个类,也会在内存中出现两个Class类的实例。
因为被具有隔离性的类装载器装载的类不会共享内存空间,使得使用一个类装载器不可能完成的任务变得可以轻而易举,
2、____________________________________________________________
unicode字符,全都是char的。没有所谓的整数等,它都叫unicode字符了,网络上传输的全都是字符。整数,只能存在于内存中,但如何区分呢?
字符流数据无边界
pc,栈的 结构是什么,线性表还是链表,用什么实现的?c么?c上面的又是用什么实现。追溯到cpu指令。
????看完jvm等后对编程有什么启发
指令存在那里,for循环里面的?jvm里面的指令如何与操作系统同联系,中间需要本地方法不?(应该不需要)
java体系结构的四个组成部分:
java语言
java calss文件格式
java API(类库,有很多抽象方法,也有很多底层的方法,比如访问系统资源I/O)
jvm 抽象的计算机,有自己的指令和内存模型,C语言就没有
java API和jvm一起组成java平台。
装载与执行
jvm包含classloader,用来装载class文件,API中只有程序执行时需要的那些类才会被装载,字节码有执行引擎来执行
执行引擎(前三种为软件实现,第四种为硬件实现)
1、一次性解释字节码:实现简单,效率低(这是“解释”技术的共性)。
2、即时编译器(just-in-time compiler),第一次执行的字节码会被编译成本地机器代码(然后缓存,以后可以重用)。效率高,但是占用内存大。
3、自适应优化器,结合1、2的优点,对频繁使用的代码编译成本地代码(10%-20%,但是它们使用的时间为80%-90%),其他仍为解释字节码。
4、硬件芯片实现,内嵌在芯片里,用本地方法执行java字节码。
三种理解:1、抽象规范,2、具体实现,3、正在运行的实例(线程)
执行引擎的行为由指令集来定义。
指令集:方法的字节码流,有指令序列构成,每个指令包含:一个单字节的指令,和后面跟随的0-n个操作数operand。
///////////////////////执行一条指令的任务之一是:决定下一条要执行的指令是什么,有三种方法:////////////////////////////
1、顺序执行。2、goto或return。3、异常。
byteStream:03 3b 84 00 01 a7 ff f9 //都是16进制的
iconst_0 //03 把int型常量0压入栈 常量0,是索引为0的常量,不是值为0,已经用javap验证。
istore_0 //3b 把int型的值存入局部变量0中
iinc 0,1 //84 00 01
goto 2 //a7 ff f9
方法
java方法:由java编写,编译成字节码,存储于class文件中
本地方法:用(C或C++或汇编)编译成与处理器相关的机器代码,保存于动态连接库中,格式是各平台专有的。
调用本地方法时,jvm装载包含这个本地方法的动态库,本地方法连接java程序与底层操作系统。
JNI(java native interface),如果希望保证平台无关性,只能通过调用java API访问底层系统资源。
类装载器calss loader
jvm会使用装载第一个类的装载器,装载它所引用的类。
JVM规范定义了两种类型的类装载器:启动类装载器(bootstrap)和用户自定义装载器(user-defined class loader)。
bootstrap是JVM自带的类装载器,用来装载核心类库,如java.lang.*等。如java.lang.Object是由bootstrap装载的。
Java提供了抽象类ClassLoader,所有用户自定义类装载器都实例化自ClassLoader的子类。 System Class Loader是一个特殊的用户自定义类装载器,由JVM的实现者提供,在编程者不特别指定装载器的情况下默认装载用户类。系统类装载器可以通过ClassLoader.getSystemClassLoader() 方法得到。
当运行一个程序的时候,JVM启动,运行bootstrapclassloader(与jvm一样用本地代码实现),该ClassLoader加载java核心API(即所有java.*开头的类,ExtClassLoader和AppClassLoader也在此时被加载),然后调用ExtClassLoader加载扩展API(例如所有javax.*开头的类和存放在JRE的ext目录下的类),最后AppClassLoader加载CLASSPATH目录下定义的Class,
似乎JVM自身的ClassLoader已经足够了,为什么我们还需要创建自己的ClassLoader呢? 因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,如果编写你自己的ClassLoader,你可以做到: 1)在执行非置信代码之前,自动验证数字签名 2)动态地创建符合用户特定需要的定制化构建类 3)从特定的场所取得java class,例如数据库中 4) 等等 事实上当使用Applet的时候,就用到了特定的ClassLoader,因为这时需要从网络上加载java class,并且要检查相关的安全信息。 目前的应用服务器大都使用了ClassLoader技术
ClassLoader Tree & Delegation Model
当你决定创建你自己的ClassLoader时,需要继承java.lang.ClassLoader或者它的子类。在实例化每个ClassLoader对象时,需要指定一个父对象;如果没有指定的话,系统自动指定ClassLoader.getSystemClassLoader()为父对象。
在Java中,java class的卸载仅仅是一种对系统的优化,有助于减少应用对内存的占用。既然是一种优化方法,那么就完全是JVM自行决定如何实现,对Java开发人员来说是完全透明的。 在什么时候一个java class/interface会被卸载呢?Sun公司的原话是这么说的:"class or interfacemay be unloaded if and only if its class loader is unreachable. Classesloaded by the bootstrap loader may not be unloaded."
通过线程上下文来加载第三方库jndi实现, 而不依赖于双亲委派. 大部分java app服务器(jboss, tomcat..)也是采用contextClassLoader来处理web服务。
运行时包。
如何回收对象:比如new Person时,20行后的代码才会引用到,gc怎么回收。
沙箱模型:沙箱,包含儿童玩耍的装置,安全环境的意思,
java沙箱基本组件
1、类装载器结构--守护被信任类的边界。
自定义java.lang.MyTest编译无语法错误,运行报
2、class文件检验器--calss结构检查(语法检查)、类型数据的语义检查
3、内置于jvm及java语言的安全特性
类型安全的引用转换、GC、数组边界检查、空引用检查
结构化的内存访问(无指针算法):class文件无内存地址,jvm装载时再分配。
本地方法必须连接,动态链接库。
4、安全管理器及JAVA API
第五章,jvm
jvm概念:
1、抽象规范
2、一个具体的实现:jvm内部如何存储数据,和具体的实现有关。
3、一个运行中的jvm实例(一个java程序,对应一个jvm实例)
初始类中的main方法,作为初始线程的起点,非守护线程。
每个jvm都有一个执行引擎,执行被装载类方法中的指令。
运行时数据区5个:方法区、堆、java栈、pc计数器、本地方法栈。
jvm规范对此描述相当抽象,细节有实现者决定。
jvm装载class文件,解析得到类型信息,将其放入到方法区,并生成Class对象(位于堆区,类型信息与Class对象什么关系,重复么?)
运行时,把创建的对象存入堆中。方法区和堆,该jvm实例中所有线程共享
pc计数器和java栈,为线程独享(私有,其他线程不可见)。大小规则(容易处理,但不灵活),不像堆内存,不会出现内存碎片。
pc的值,指示下一条将被执行的指令。(指令存于pc中?)
cpu的pc是指令地址计数器,存放指令地址,那么指令有存在哪里,java指令只有一个字节,是否直接存指令本身
java栈,存储该线程中java方法的调用状态(包括它的局部变量,入参,返回值,中间结果等)
java栈有栈帧(stack frame)组成,一个栈帧对应一个调用状态。调用一个方法,就压入一个栈帧。
栈帧包含两部分:局部变量,和操作数栈。局部变量等,属于运行时数据。
本地方法的调用状态存于本地方法栈。
数据类型:
基本类型的变量持有原始值(原始数据)
引用类型的变量持有引用值(地址)
retrunAddress类型:jvm内部使用,用来实现finally字句
方法区类型信息:内存不连续,可伸缩
这个类型及其直接超类的权限定名。class文件中用/代替. 如java/lang/Object
这个类型是类类型还是接口类型(可见abstract和interface本质上不同)
这个类型的访问修饰符:public、static、final等
直接接口的(多个)的权限定名的有序列表
field信息
字段名、字段类型、修饰符
method信息
方法名、返回类型、参数个数及类型、修饰符。如果不是本地方法或抽象方法,还有以下信息:
方法的字节码bytecode
操作数栈和该方法栈帧中局部变区的大小
异常表
这个类型的常量池
直接常量(String、integer等常量),对其他类型、字段、方法的符号引用。因此在java动态链接中起核心作用。
池中的数据如数组一样通过索引访问。
除常量以外的所有类(静态)变量
编译时常量:final声明以及,编译时已知的值初始化的类变量。
一个到类ClassLoader的引用
jvm必须跟踪ClassLoader,在动态链接期间,用此来装载它所引用、依赖的类
一个到类Class的引用
每一个被装载的类型(类、接口),jvm都会为其创建一个java.lang.Class实例。
jvm还会以某种方式将Class实例与类信息关联起来。
Class类使得运行程序可以访问方法区中保存的信息。
方法表:
为了提高访问速度,jvm会为每个装载的非抽象类,生成一个方法表,作为类信息的一部分,
方法表是一个数组,元素是可能被调用用实例方法的直接引用。
堆:内存不连续,可伸缩
jvm规范没有规定java对象如何在堆中表示
1、把堆分为:句柄池、对象池。对象引用先指向句柄池,句柄池有2个条目:指向对象的引用,指向类信息的引用
2、对象中包含指向类信息的指针。
这样就可以实现动态绑定了。
堆中对象数据都有一个对象锁(互斥对象,只有一个线程可以拥有),逻辑上还与等待集合(wait set)数据关联。
PC:应该连续
栈:内存不连续,可伸缩
栈的元素--栈帧:大小确定,堆内存的对象大小不确定。
1、局部变量区:包含方法的入参和局部变量
1个字长为单位,从0开始计数的数组,字节码指令opcode通过索引(偏移量?)取数据
如:runInstanceMethod(char c,int i),局部变量区按索引如下存储:
0 reference(实例对象的引用,对象不会被拷贝)
1 int (char c 存储时会转换成int)
2 int(int i)
2、操作数栈:也是一个字长为单位,但是是栈,不是数组
3、帧数据区:
用来支持常量池解析(入口地址),正常方法返回,异常方法派发机制
每个方法被执行的时候,都会同时创建一个帧(Frame)用于存储本地变量表、操作栈、动态链接、方法出入口等信息。每一个方法的调用至完成,就意味着一个帧在VM栈中的入栈至出栈的过程。本地变量表存放了编译期可知的各种标量类型(boolean、byte、char、short、int、float、long、double)、对象引用(不是对象本身,仅仅是一个引用指针)、方法返回地址等。其中long和double会占用2个本地变量空间(32bit),其余占用1个。
本地方法栈
字节码(java方法),
第六章:class文件
ClassFile {
u4 magic;//前4个字符永远是0xCAFEBABE
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
线程
同步,syncronized
协作,wait,notifyall
保存到什么地方(另类说法)
程序运行时,我们最好对数据保存到什么地方做到心中有数。特别要注意的是内存的分配。有六个地方都可以保存数据:
(1) 寄存器。最快,位于CPU。寄存器是根据需要由编译器分配。我们对此没有直接的控制权。
(2) 堆栈。可通过它的“堆栈指针”获得处理的直接支持。
(3) 堆。
(5) 常数存储。常数值通常直接置于程序代码内部。因为它们永远都不会改变
(6) 非RAM存储。若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。比如“流式对象”和“固定对象”。对于流式对象,对象会变成字节流,通常会发给另一台机器。而对于固定对象,对象保存在磁盘中。
3、待整理____________________________________________________________
经典书不是看一两遍就可以的。要反反复复地体会理解。结合JMX
内存模型,class文件。?
一个对象占用多大的内存。?
计算机的基本构成是:运算器、控制器、存储器、输入和输出设备
JVM是一个内存中的虚拟机
编译环境
.java文件--java编译器--.class文件
字节码.class文件本地或网络
运行期环境(java平台)
类装载器字节码的验证--装入.class文件和库文件--java解释器/即时编译器--
运行数据区5个(除了本地方法栈)--执行引擎--native interface -- native libray
问:OutOfMemory错误分几种?
答:分两种,分别是“OutOfMemoryError:java heap size”和”OutOfMemoryError: PermGen space”,两种都是内存溢出,heap size是说申请不到新的内存了,这个很常见,检查应用或调整堆内存大小。
“PermGen space”是因为永久存储区满了,这个也很常见,一般在热发布的环境中出现,是因为每次发布应用系统都不重启,久而久之永久存储区中的死对象太多导致新对象无法申请内存,一般重新启动一下即可。
问:为什么会产生StackOverflowError?
答:因为一个线程把Stack内存全部耗尽了,一般是递归函数造成的。栈和线程、方法、基本类型值、对象符号引用有关
JIT编译器
java开始只是解释型的,速度慢,为此后来sun在JVM上提供一个工具,把字节码编译成原生码,下次你来访问的时候直接访问原生码就成了
引用类型包括:类类型,接口类型和数组
栈是运行时的单位,而堆是存储的单位.栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。
栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等.
当我们把对象拆开,你会发现,对象的属性其实就是数据,存放在堆中;而对象的行为(方法),就是运行逻辑,放在栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。不得不承认,面向对象的设计,确实很美。
程序运行永远都是在栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题,传对象不显示(对象大小不规则)。线程拷贝的对象副本在线程工作内存中,共享对象在主内存。
对象,从某种意义上说,是由基本类型组成的。可以把一个对象看作为一棵树,对象的属性如果还是对象,则还是一颗树(即非叶子节点),基本类型则为树的叶子节点。
垃圾回收策略:会暂停程序执行
按照基本回收策略分
1、引用计数(Reference Counting):
比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。
此算法最致命的是无法处理循环引用的问题,即A,B相互引用。
2、标记-清除(Mark-Sweep)+复制(Copying)
3、标记-整理(Mark-Compact)
4、火车头算法。
按分区对待的方式分
增量收集(Incremental Collecting):实时垃圾回收算法,即:在应用进行的同时进行垃圾回收。不知道什么原因JDK5.0中的收集器没有使用这种算法的。
分代收集(Generational Collecting):基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的。
按系统线程分:串行收集(单线程),并行收集。并发收集:相对于串行收集和并行收集而言,前面两个在进行垃圾回收工作时,需要暂停整个运行环境,而只有垃圾回收程序在运行,因此,系统在垃圾回收时会有明显的暂停,而且暂停时间会因为堆越大而越长。
垃圾回收从Java栈开始,获取哪些对象正在被使用,因为栈是真正进行程序执行地方。并发收集器
可以保证大部分工作都并发进行(应用不停止),垃圾回收只暂停很少的时间,此收集器适合对响应时间要求比较高的中、大规模应用。使用-XX:+UseConcMarkSweepGC打开
最大垃圾回收暂停:-XX:MaxGCPauseMillis
暂停问题:当堆空间持续增大时,垃圾回收的时间也将会相应的持续增大,对应应用暂停的时间也会相应的增大
内存碎片:并发垃圾回收算法,解决了暂停的问题,但复杂度高,回收效率低下。
String对象如何不变,他不改变参数,返回时new一个新的。
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,操作系统对一个进程内的线程数是有限制的,不能无限生成,经验值在3000~5000左右
吞吐量最大化:GC时间占总运行时间最小化
年轻代大小选择
响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用
年轻代大小选择
响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
年老代大小选择
响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。
JVM判断2个类相同的条件:
1、类的全名是否相同,还要看加载此类的类加载器是否一样
所有Java代码都要引用java.lang.Object类,如果用自己的类加载器来完成的话,就会存在很多版本的java.lang.Object类。通过代理模式,对于Java核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。
不同的类加载器为相同名称的类创建了额外的名称空间。相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。不同类加载器加载的类之间是不兼容的,这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间。这种技术在许多框架中都被用到,后面会详细介绍。
方法-对应线程-main是主线程。
SPI(Service Provider Interface,如JDBC,JNDI) 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式无法解决这个问题,由此产生了context class loader,是从 JDK 1.2 开始引入的
类加载器是 Java 语言的一个创新。它使得动态安装和更新软件组件成为可能(不重启程序,只需要从服务器上下载新的class文件即可)。