Java类别问题
1. String与StringBuilder、StringBuffer的区别
如果要操作少量的数据用 = String
单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
2. Collection类,Collection包结构,与Collections的区别
Collection是集类,包含List有序列表,Set无序集合以及Map双列集合
Collection是集合类的上级接口,子接口主要有Set 和List、Map。
Collections是针对集合类的一个帮助类,提供了操作集合的工具方法:一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。
3. java哪些集合类是线程安全的
vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
statck:堆栈类,先进后出
enumeration:枚举,相当于迭代器
hashtable:就比hashmap多了个线程安全
4. 线程安全是什么意思
就是线程同步的意思,就是当一个程序对一个线程安全的方法或者语句进行访问的时候,其他的不能再对他进行操作了,必须等到这次访问结束以后才能对这个线程安全的方法进行访问
5. 九种基本数据类型的大小,以及他们的封装类。
java提供了一组基本数据类型,包括boolean, byte, char, short, int, long, float, double, void. 同时,java也提供了这些类型的封装类,分别为Boolean, Byte, Character, Short, Integer, Long, Float, Double, Void
既然提供了基本类型,为什么还要使用封装类呢
比如,如果想使用List来保存数值,由于List中只能添加对象,另外,有些情况下,我们也会编写诸如func(Object o)的这种方法
6. Switch能否用string做参数
在 Java 7之前,switch 只能支持 byte、short、char、int或者其对应的封装类以及 Enum 类型。在 Java 7中,String支持被加上了。
7. equals与==的区别。
1.==是一个运算符。
2.Equals则是Object对象的方法,可以.(点)出来。
8. Java的四种引用,强弱软虚,用到的场景
1.强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就 类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
2.软引用,如果一个对象只具有软引用,那就类似于可有可物的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。
3.弱引用,如果一个对象只具有弱引用,那就类似于可有可物的生活用品。 弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
4.虚引用,"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
9. Hashcode的作用
Hashcode这个方法是用来鉴定2个对象是否相等的,hashcode方法一般用户不会去调用,比如在hashmap中,由于key是不可以重复的,他在判断key是不是重复的时候就判断了hashcode这个方法,而且也用到了equals方法。
10. ArrayList、LinkedList、Vector的区别
ArrayList,Vector底层是由数组实现,LinkedList底层是由双线链表实现,从底层的实现可以得出它们的性能问题,ArrayList,Vector插入速度相对较慢,查询速度相对较快,而LinkedList插入速度较快,而查询速度较慢。再者由于Vevtor使用了线程安全锁,所以ArrayList的运行效率高于Vector。
11. Map、Set、List、Queue、Stack的特点与用法
Set集合类似于一个罐子,"丢进"Set集合里的多个对象之间没有明显的顺序。
List集合代表元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。
Stack是Vector提供的一个子类,用于模拟"栈"这种数据结构(LIFO后进先出)
Queue用于模拟"队列"这种数据结构(先进先出 FIFO)。
Map用于保存具有"映射关系"的数据,因此Map集合里保存着两组值
12. HashMap和HashTable的区别
Hashtable是基于陈旧的Dictionary类的,HashMap是Map接口的一个实现
Hashtable的方法是线程同步的,而HashMap的方法不是。
只有HashMap可以让你将空值作为一个表的条目的key或value
13. TreeMap、HashMap、LindedHashMap的区别
Hashmap 是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。
LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排序
TreeMap取出来的是排序后的键值对。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。
14. try catch finally,try里有return,finally还执行么
a.不管有木有出现异常,finally块中代码都会执行
b.当try和catch中有return时,finally仍然会执行
c.finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的
d.finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值
15. OOM你遇到过哪些情况
java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。
java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。
java.lang.StackOverflowError ------> 不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。
16. Java面向对象的三个特征与含义
封装性:它是将类的一些敏感信息隐藏在类的类部,不让外界直接访问到
继承性:子类通过一种方式来接受父类所有的公有的,受保护的成员变量和成员方法
多态性:程序在运行的过程中,同一种类型在不同的条件下表现不同的结果
17. Override和Overload的含义去区别
方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被“屏蔽”了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。
18. Interface与abstract类的区别
接口可以多重继承,抽象类不可以
接口定义方法,不给实现;而抽象类可以实现部分方法
接口中基本数据类型的数据成员,都默认为static和final,抽象类则不是
19. Static class 与non static class的区别
内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。
非静态内部类能够访问外部类的静态和非静态成员。静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员。一个非静态内部类不能脱离外部类实体被创建,一个非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面。
20. java多态的实现原理
ⅰ.设计时多态:方法【重载】实现的多态
ⅱ.运行时多态:方法【重写】实现的多态
21. 实现多线程的两种方法:Thread与Runable
使用Runnable接口
实际工作中,几乎所有的多线程应用都用实现Runnable这种方式。
Runnable适合多个相同程序代码的线程去处理同一资源的情况。把虚拟CPU(线程)同程序的代码、数据有效的分离,较好的体现了面向对象的设计思想。
避免由于Java的单继承特性带来的局限性。也就是如果新建的类要继承其他类的话,因为JAVA中不支持多继承,就只能实现java.lang.Runnable接口。
有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。
继承Thread类
不能再继承他类了。
编写简单,可以直接操纵线程,无需使用Thread.currentThread()。
22. 线程同步的方法:sychronized、lock、reentrantLock等
sychronized是java中最基本同步互斥的手段,可以修饰代码块,方法,类,在修饰代码块的时候需要一个reference对象作为锁的对象,在修饰方法的时候默认是当前对象作为锁的对象,在修饰类时候默认是当前类的Class对象作为锁的对象.
ReentrantLock除了synchronized的功能,多了三个高级功能.
等待可中断,在持有锁的线程长时间不释放锁的时候,等待的线程可以选择放弃等待
公平锁, 按照申请锁的顺序来一次获得锁称为公平锁.synchronized的是非公平锁,ReentrantLock可以通过构造函数实现公平锁
绑定多个Condition. 通过多次newCondition可以获得多个Condition对象,可以简单的实现比较复杂的线程同步的功能.通过await(),signal();
23. Java锁的等级
方法锁,synchronized标记的方法
对象锁,在方法上加了synchronized的锁,或者synchronized(this)的代码段
类锁,在代码中的方法上加了static和synchronized的锁,因为在静态方法中加同步锁会锁住整个类
24. 写出生产者消费者模式
阻塞队列实现生产者消费者模式超级简单,它提供开箱即用支持阻塞的方法put()和take(),开发者不需要写困惑的wait-nofity代码去实现通信。BlockingQueue 一个接口,Java5提供了不同的现实,如ArrayBlockingQueue和LinkedBlockingQueue,两者都是先进先出(FIFO)顺序。而ArrayLinkedQueue是自然有界的,LinkedBlockingQueue可选的边界。
25. ThreadLocal的设计理念与作用
ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
26. ThreadPool用法与优势
线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。
FixedThreadPool(int nThreads): 创建一个可重用的固定线程数的线程池,如果池中所有的nThreads个线程都处于活动状态时提交任务(任务通常是Runnable或Callable对象), 任务将在队列中等待, 直到池中出现可用线程
CachedThreadPool(): 调用此方法创建的线程池可根据需要自动调整池中线程的数量,执行任务时将重用存在先前创建的线程(如果池中存在可用线程的话). 如果池中没有可用线程, 将创建一个新的线程, 并将其添加到池中. 池中的线程超过60秒未被使用就会被销毁, 因此长时间保持空闲的
SingleThreadExecutor(): 创建一个单线程的Executor. 这个Executor保证按照任务提交的顺序依次执行任务.
ScheduledThreadPool(int corePoolSize): 创建一个可重用的固定线程数的线程池. ScheduledExecutorService是ExecutorService的子接口, 调用ScheduledExecutorService的相关方法, 可以延迟或定期执行任务.
以上静态方法均使用默认的ThreadFactory(即Executors.defaultThreadFactory()方法的返回值)创建线程, 如果想要指定ThreadFactory, 可调用他们的重载方法.通过指定ThreadFactory, 可以定制新建线程的名称, 线程组, 优先级, 守护线程状态等.
如果Executors提供的创建线程池的方法无法满足要求, 可以使用ThreadPoolExecutor类创建线程池.
27. wait()和sleep()的区别
sleep指线程被调用时,占着CPU不工作,形象地说明为“占着CPU睡觉”,此时,系统的CPU部分资源被占用,其他线程无法进入,会增加时间限制。
wait指线程处于进入等待状态,形象地说明为“等待使用CPU”,此时线程不占用任何资源,不增加时间限制
28. foreach与正常for循环效率对比
针对列表的 foreach的效率是最低, 耗时是普通for循环的2倍以上。个人理解它的实现应该和iterator相似
29. Java IO与NIO
Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
30. 反射的作用于原理
JAVA反射(放射)机制:Reflection,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。
用途:Java反射机制主要提供了以下功能: 在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。
31. 泛型常用特点,List<String>能否转为List<Object>
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)
在Java的泛型接口中 把List < String > 转换成List < Object > 是可以的。
32. 解析XML的几种方式的原理与特点:DOM、SAX
Dom解析 在内存中创建一个DOM树,该结构通常需要加载整个文档然后才能做工作。由于它是基于信息层次的,因而DOM被认为是基于树或基于对象的,树在内存中是持久的,因此可以修改它以便应用程序能对数据和结构作出更改能随机访问文件内容,也可以修改原文件内容
SAX解析 SAX处理的优点非常类似于流媒体的优点。分析能够立即开始,而不是等待所有的数据被处理。SAX解析器采用了基于事件的模型,它在解析XML文档的时候可以触发一系列的事件,当发现给定的tag的时候,它可以激活一个回调方法,告诉该方法制定的标签已经找到。而且,由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中。这对于大型文档来说是个巨大的优点线性解析,不能随机访问,也无法修改原文件
33. 设计模式:单例、工厂、适配器、责任链、观察者等等
34. JNI的使用
JNI作为java和操作系统间的一个直接接口,可以通过JNI使得java直接调用操作系统的资源。目前JNI只能通过c/C++实现,因为jni只是对操作系统资源调用的一个桥接过程。所以理论上在windows下只要是dll文件均可以被调用。
jni一般有以下一些应用场景
1.高性能 ,在一些情况下因为处理运算量非常大,为了获取高性能,直接使用java是不能胜任的,如:一些图形的处理
2.调用一些硬件的驱动或者一些软件的驱动,比如调用一些外部系统接口的驱动,如:读卡器的驱动,OCI驱动
3.需要使用大内存,远远超过jvm所能分配的内存,如:进程内Cache
4.调用C或者操作系统提供的服务,如:java调用搜索服务,其中搜索是由C/C++实现的,不过这个一般可以设计成更加通用的方式,比如soa的方式
所有这些场景的前提是牺牲了java代码的可移植性,不同的os,甚至版本都需要写不同版本的native实现
35. mysql机制
InnoDB与Myisam
Innodb 支持事务功能,Myisam 不支持。
Myisam 的执行速度更快,性能更好。
InnoDB 为行级锁,myisam 为表级锁
MyISAM:如果执行大量的SELECT,MyISAM是更好的选择
InnoDB:如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表
MyISAM:select count(*) from table,MyISAM只要简单的读出保存好的行数,注意的是,当count(*)语句包含 where条件时,两种表的操作是一样的
InnoDB : InnoDB 中不保存表的具体行数,也就是说,执行select count(*) from table时,InnoDB要扫描一遍整个表来计算有多少行
36. Servlet的生命周期、Servlet是否线程安全的
a.在servlet容器启动时初始化。在web.xml <servlet> 中配置<load-on-startup>1</load-on-startup>表示在容器启动时初始化。默认没有此置
b.servlet在第一次被访问时初始化。即创建唯一的servlet实例。(单例多线程下面会说)
c.当有请求访问该servlet是,servlet容器就会创建针对于这个请求的servletRequest于servletResponse,然后servlet的service方法被调用。当容器把servlet生成的响应结果发送给客户,容器就会销毁request和response对象
d.容器在销毁该实例前调用servlet的destroy方法(释放servlet所占用的资源,如关闭流和数据库连接),此外还会销毁与servlet对象关联的ServletConfig对象。
e.servlet类只创建一个实例,对于可与客户端的并发访问,它是线程不安全的。
f.servlet的处理方式是,每次访问时重新起一线程执行service方法。所以要想保证servlet的线程安全,不应该在servlet中定义实例变量。
g.当然完全可以通过加锁保证线程安全,但对于成千上万的并发访问,性能下降。
JVM类别问题
1. 内存模型以及分区,需要详细到每个区放什么。
程序计数器:当前线程执行的字节码的行号指示器,线程私有
JAVA虚拟机栈:Java方法执行的内存模型,每个Java方法的执行对应着一个栈帧的进栈和出栈的操作。
本地方法栈:类似“ JAVA虚拟机栈 ”,但是为native方法的运行提供内存环境。
JAVA堆:对象内存分配的地方,内存垃圾回收的主要区域,所有线程共享。可分为新生代,老生代。
方法区:用于存储已经被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Hotspot中的“永久代”。
运行时常量池:方法区的一部分,存储常量信息,如各种字面量、符号引用等。
直接内存:并不是JVM运行时数据区的一部分, 可直接访问的内存, 比如NIO会用到这部分。
2. 堆里面的分区:Eden,survival from to,老年代,各自的特点。
3. 对象创建方法,对象的内存分配,对象的访问定位。
4. GC的两种判定方法:引用计数与引用链。
引用计数方式最基本的形态就是让每个被管理的对象与一个引用计数器关联在一起,该计数器记录着该对象当前被引用的次数,每当创建一个新的引用指向该对象时其计数器就加1,每当指向该对象的引用失效时计数器就减1。当该计数器的值降到0就认为对象死亡。
Java的内存回收机制可以形象地理解为在堆空间中引入了重力场,已经加载的类的静态变量和处于活动线程的堆栈空间的变量是这个空间的牵引对象。这里牵引对象是指按照Java语言规范,即便没有其它对象保持对它的引用也不能够被回收的对象,即Java内存空间中的本原对象。当然类可能被去加载,活动线程的堆栈也是不断变化的,牵引对象的集合也是不断变化的。对于堆空间中的任何一个对象,如果存在一条或者多条从某个或者某几个牵引对象到该对象的引用链,则就是可达对象,可以形象地理解为从牵引对象伸出的引用链将其拉住,避免掉到回收池中
5. GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方,如果让你优化收集方法,有什么思路?
标记清除算法是最基础的收集算法,其他收集算法都是基于这种思想。标记清除算法分为“标记”和“清除”两个阶段:首先标记出需要回收的对象,标记完成之后统一清除对象。它的主要缺点:①.标记和清除过程效率不高 。②.标记清除之后会产生大量不连续的内存碎片。
标记整理,标记操作和“标记-清除”算法一致,后续操作不只是直接清理对象,而是在清理无用对象完成后让所有存活的对象都向一端移动,并更新引用其对象的指针。主要缺点:在标记-清除的基础上还需进行对象的移动,成本相对较高,好处则是不会产生内存碎片。
复制算法,它将可用内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次理掉。这样使得每次都是对其中的一块进行内存回收,不会产生碎片等情况,只要移动堆订的指针,按顺序分配内存即可,实现简单,运行高效。主要缺点:内存缩小为原来的一半。
6. GC收集器有哪些?CMS收集器与G1收集器的特点。
单线程收集器,使用单线程去完成所有的gc工作,没有线程间的通信,这种方式会相对高效
并行收集器,使用多线程的方式,利用多CUP来提高GC的效率,主要以到达一定的吞吐量为目标
并发收集器,使用多线程的方式,利用多CUP来提高GC的效率,并发完成大部分工作,使得gc pause短
G1具备如下特点:并行与并发、分代收集、空间整合、可预测的停顿
CMS具备了并发收集、低停顿的优点、CMS收集器对CPU资源非常敏感、CMS收集器无法处理浮动垃圾、收集结束时会产生大量空间碎片
7. Minor GC与Full GC分别在什么时候发生?
Minor GC:通常是指对新生代的回收。指发生在新生代的垃圾收集动作,因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快
Major GC:通常是指对年老代的回收。
Full GC:Major GC除并发gc外均需对整个堆进行扫描和回收。指发生在老年代的 GC,出现了 Major GC,经常会伴随至少一次的 Minor GC(但非绝对的,在 ParallelScavenge 收集器的收集策略里就有直接进行 Major GC 的策略选择过程) 。MajorGC 的速度一般会比 Minor GC 慢 10倍以上。
8. 几种常用的内存调试工具:jmap、jstack、jconsole。
jmap(linux下特有,也是很常用的一个命令)观察运行中的jvm物理内存的占用情况。
参数如下:
-heap:打印jvm heap的情况
-histo:打印jvm heap的直方图。其输出信息包括类名,对象数量,对象占用大小。
-histo:live :同上,但是只答应存活对象的情况
-permstat:打印permanent generation heap情况
jstack(linux下特有)可以观察到jvm中当前所有线程的运行情况和线程当前状态
jconsole一个图形化界面,可以观察到java进程的gc,class,内存等信息
jstat最后要重点介绍下这个命令。这是jdk命令中比较重要,也是相当实用的一个命令,可以观察到classloader,compiler,gc相关信息
具体参数如下:
-class:统计class loader行为信息
-compile:统计编译行为信息
-gc:统计jdk gc时heap信息
-gccapacity:统计不同的generations(不知道怎么翻译好,包括新生区,老年区,permanent区)相应的heap容量情况
-gccause:统计gc的情况,(同-gcutil)和引起gc的事件
-gcnew:统计gc时,新生代的情况
-gcnewcapacity:统计gc时,新生代heap容量
-gcold:统计gc时,老年区的情况
-gcoldcapacity:统计gc时,老年区heap容量
-gcpermcapacity:统计gc时,permanent区heap容量
-gcutil:统计gc时,heap情况
-printcompilation:不知道干什么的,一直没用过。
9. 类加载的五个过程:加载、验证、准备、解析、初始化。
“加载”(Loading)阶段是“类加载”(Class Loading)过程的第一个阶段,在此阶段,虚拟机需要完成以下三件事情:
1、 通过一个类的全限定名来获取定义此类的二进制字节流。
2、 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3、 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
类初始化是类加载过程的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。
10. 双亲委派模型:Bootstrap ClassLoader、Extension ClassLoader、ApplicationClassLoader。
1、启动类加载器,负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即时放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被java程序直接引用。
2、扩展类加载器:负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用该类加载器。
3、应用程序类加载器:负责加载用户路径上所指定的类库,开发者可以直接使用这个类加载器,也是默认的类加载器。
三种加载器的关系:启动类加载器->扩展类加载器->应用程序类加载器->自定义类加载器。
这种关系即为类加载器的双亲委派模型。其要求除启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不以继承关系实现,而是用组合的方式来复用父类的代码。
双亲委派模型的工作过程:如果一个类加载器接收到了类加载的请求,它首先把这个请求委托给他的父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它在搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
好处:java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar中,无论哪个类加载器要加载这个类,最终都会委派给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果用户自己写了一个名为java.lang.Object的类,并放在程序的Classpath中,那系统中将会出现多个不同的Object类,java类型体系中最基础的行为也无法保证,应用程序也会变得一片混乱。
实现:在java.lang.ClassLoader的loadClass()方法中,先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败,则抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。
11. 分派:静态分派与动态分派。
静态分派与重载有关,虚拟机在重载时是通过参数的静态类型,而不是运行时的实际类型作为判定依据的;静态类型在编译期是可知的;
动态分派与重写(Override)相关,invokevirtual(调用实例方法)指令执行的第一步就是在运行期确定接收者的实际类型,根据实际类型进行方法调用;