抽象、继承、封装、多态性
修饰符 | 当前类 | 当前类 | 当前类 | 当前类 |
---|---|---|---|---|
private | √ | |||
default | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
相同点:
&和&&都可以用作逻辑与的运算符,表示逻辑与(and)。
不同点:
&&具有短路的功能,而&不具备短路功能。
当&运算符两边的表达式的结果都为true时,整个运算结果才为true。而&&运算符第一个表达式为false时,则结果为false,不再计算第二个表达式。
&还可以用作位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如:0x31 & 0x0f的结果为0x01。
Math.round(11.5)的返回值是12,Math.round(-11.5)的返回值是-11
2 << 3(左移3位相当于乘以2的3次方,右移3位相当于除以2的3次方)
数组没有length()方法,有length的属性。String有length()方法。
String 不属于基础类型,基础类型有 8 种:byte、boolean、char、short、int、float、long、double,而 String 属于对象。
String 类是final类,不可以被继承。
两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象。
不对,如果两个对象x和y满足x.equals(y) == true,它们的哈希码(hash code)应当相同。
1)功能不同 "=="是判断两个变量或实例是不是指向同一个内存空间。 "equals"是判断两个变量或实例所指向的内存空间的值是不是相同。
2)定义不同 "equals"在JAVA中是一个方法。 "=="在JAVA中只是一个运算符合。 例子: Student student1 = new Student()…
3)运行速度不同 "“比"equals"运行速度快,因为”"只是比较引用。
不对,两个对象的 hashCode()相同,equals()不一定 true。
String和StringBuffer/StringBuilder,它们可以储存和操作字符串。其中String是只读字符串,也就意味着String引用的字符串内容是不能被改变的,而StringBuffer/StringBuilder类表示的字符串对象可以直接进行修改。
StringBuilder是Java 5中引入的,它和StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被synchronized修饰(非同步),因此它的效率也比StringBuffer要高。
indexOf():返回指定字符的索引。
charAt():返回指定索引处的字符。
replace():字符串替换。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后的字符串数组。
getBytes():返回字符串的 byte 类型数组。
length():返回字符串长度。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
substring():截取字符串。
equals():字符串比较。
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型
构造器不能被继承,因此不能被重写,但可以被重载。
静态变量是被static修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝。
实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。
有两种方式:
1)实现Cloneable接口并重写Object类中的clone()方法。
2)实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员。
普通类不能包含抽象方法,抽象类可以包含抽象方法。
抽象类不能直接实例化,普通类可以直接实例化。
不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类。
实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
构造函数:抽象类可以有构造函数;接口不能有。
main 方法:抽象类可以有 main 方法,并且我们能运行它;接口不能有 main 方法。
实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。
final 修饰的类叫最终类,该类不能被继承。
final 修饰的方法不能被重写。
final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。
final:修饰符(关键字)有三种用法:如果一个类被声明为final,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。将变量声明为final,可以保证它们在使用中不被改变,被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为final的方法也同样只能使用,不能在子类中被重写。
finally:通常放在try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。
finalize:Object类中定义的方法,Java中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行其他清理工作。
所谓多态,指的就是父类引用指向子类对象,调用方法时会调用子类的实现而不是父类的实现。多态的实现的关键在于“动态绑定”。
clone(), equals(), hashCode(), toString(), notify(), notifyAll(), wait(), finalize(), getClass()
泛型即参数化类型,在创建集合时,指定集合元素的类型,此集合只能传入该类型的参数。类型擦除:java编译器生成的字节码不包含泛型信息,所以在编译时擦除:1.泛型用最顶级父类替换;2.移除。
并发编程中:原子性问题,可见性问题,有序性问题。
volatile关键字能保证可见性,字能禁止指令重排序,但是不能保证原子性。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。在生成的会变语句中加入Lock关键字和内存屏障。
Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题。用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而用Lock需要我们手动释放锁。
能,Java 中可以创建 volatile 类型数组,不过只是一个指向数组的引用,而不是整个数组。我的意思是,如果改变引用指向的数组,将会受到 volatile 的保护,但是如果多个线程同时改变数组的元素,volatile 标示符就不能起到之前的保护作用了
集合,线性结构(数组,队列,链表和栈),树形结构,图状结构。
Java 中的 TreeMap 是使用红黑树实现的。
poll() 和remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。
在Java 6中Arrays.sort()和Collections.sort()使用的是MergeSort,而在Java 7中,内部实现换成了TimSort,其对对象间比较的实现要求更加严格。
HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的。
HashMap最多只允许一条记录的键为null,允许多条记录的值为null。
HashMap非线程安全。ConcurrentHashMap线程安全。
解决碰撞:当出现冲突时,运用拉链法,将相同的结点链接在一个单链表中,散列表长m,则定义一个由m个头指针组成的指针数组T,地址为i的结点插入以T(i)为头指针的单链表中。Java8中,冲突的元素超过限制(8),用红黑树替换链表。
Vector属于线程安全级别的,但是大多数情况下不使用Vector,因为线程安全需要更大的系统开销。
历史原因: Hashtable继承Dictonary类, HashMap继承自abstractMap
HashMap允许空的键值对, 但最多只有一个空对象,而HashTable不允许。
HashTable同步,而HashMap非同步,效率上比HashTable要高
线程不安全的HashMap
在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。
效率低下的HashTable
HashTable使用synchronized来保证线程的安全,但是在线程竞争激烈的情况下HashTable的效率非常低下。当一个线程访问HashTable的同步方法,其他方法访问HashTable的同步方法时,会进入阻塞或者轮询状态。如果线程1使用put进行元素添加,线程2不但不能用put方法添加于元素同是也无法用get方法来获取元素,所以竞争越激烈效率越低。
ConcurrentHashMap的锁分段技术
HashTable容器在竞争激烈的并发环境效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,假如容器有多把锁,每一把锁用于锁住容器中一部分数据,那么多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效提高并发访问率,这就是ConcurrentHashMap的锁分段技术。将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一段数据的时候,其他段的数据也能被其他线程访问。
ArrrayList 底层的数据结构是数组,支持随机访问,而 LinkedList 的底层数据结构书链表,不支持随机访问。
ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。
LinkedList是双向链表
Comparable 接口用于定义对象的自然顺序,是排序接口,而 comparator 通常用于定义用户定制的顺序,是比较接口。我们如果需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口),那么我们就可以建立一个“该类的比较器”来进行排序。Comparable 总是只有一个,但是可以有多个 comparator 来定义对象的顺序。
Collection是Java集合框架中的基本接口。
Collections是Java集合框架提供的一个工具类,其中包含了大量用于操作或返回集合的静态方法。
List,Set都是继承自Collection接口
List特点:元素有放入顺序,元素可重复
Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。
Arraylist:
优点:ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。
缺点:因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低。
LinkedList:
优点:LinkedList基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址,对于新增和删除操作add和remove,LinedList比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景。
缺点:因为LinkedList要移动指针,所以查询操作性能比较低。
适用场景分析:
当需要对数据进行对此访问的情况下选用ArrayList,当需要对数据进行多次增加删除修改时采用LinkedList。
1、sleep()方法
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 让其他线程有机会继续执行,但它并不释放对象锁。也就是如果有Synchronized同步块,其他线程仍然不能访问共享数据。注意该方法要捕获异常
比如有两个线程同时执行(没有Synchronized),一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完成后,低优先级的线程才能执行;但当高优先级的线程sleep(5000)后,低优先级就有机会执行了。
总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。
2、yield()方法
yield()方法和sleep()方法类似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也和sleep()方法不同。
3、join()方法
Thread的非静态方法join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前,B不能工作。
Thread t = new MyThread(); t.start(); t.join();
保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。
虽然两者都是用来暂停当前运行的线程,但是 sleep() 实际上只是短暂停顿,因为它不会释放锁,而 wait() 意味着条件等待,这就是为什么该方法要释放锁,因为只有这样,其他等待的线程才能在满足条件时获取到该锁。
1)继承Thread类,重写run函数
2)实现Runnable接口,重写run函数
3)实现Callable接口,重写call函数
Java的线程是通过java.lang.Thread类来实现的。VM启动时会有一个由主方法所定义的线程。可以通过创建Thread的实例来创建新的线程。每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。
进程值运行中的程序(独立性,动态性,并发性),线程指进程中的顺序执行流。区别是:1.进程间不共享内存 2.创建进程进行资源分配的代价要大得多,所以多线程在高并发环境中效率高。
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
新建状态:使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
就绪状态:当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
运行状态:如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞状态:如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
可重入性:
ReenTrantLock和synchronized使用的锁都是可重入的,两者都是同一个线程每进入一次,锁的计数器都自增1,所以等到锁的计数器下降为0时才能释放锁。
锁的实现:
synchronized是依赖JVM实现的,ReenTrantLock是JDK实现的,类似于操作系统控制实现和用户自己写代码实现。
性能的区别:
synchronized在JDK5优化后,两者性能差不多了。如果两种方法都可以使用的情况下,官方建议使用synchronized,因为使用便利。
功能的区别:
synchronized由编译器加锁和释放,默认是非公平锁,ReenTrantLock手动加锁和释放锁,如果忘记释放容易引起死锁,但是对于粒度控制强于synchronized关键字。
ReenTrantLock可以指定是公平锁还是非公平锁,synchronized只能是非公平锁,所谓的公平锁就是先等待的线程先获取锁。
ReenTrantLock提供了中断锁和等待锁的功能,通过lock.lockInterruptibly()实现中断锁,通过lock.tryLock()实现等待锁。
ReenTrantLock提供了一个Condition类,实现了线程之间的通信。
同步:调用方需要主动等待结果的返回。
异步:不需要主动等待结果的返回,而是通过其他手段,比如状态通知,回调函数等。
阻塞:是指结果返回之前,当前线程被挂起,不做任何事。
非阻塞:是指结果在返回之前,线程可以做一些其他事,不会被挂起。
1、在主函数中使用join()方法
t1.start();
t2.start();
t3.start();
t1.join();//不会导致t1和t2和t3的顺序执行
t2.join();
t3.join();
System.out.println("Main finished");
2、使用CountDownLatch
public class WithLatch{
public static void main(String[] args){
CountDownLatch latch = new CountDownLatch(3);
for(int i=0;i<3;i++){
new ChildThread(i,latch).start();
}
try{
latch.await();
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("Main finished");
}
static calss ChildThread extends Thread{
private int id = -1;
private CountDownLatch latch = null;
public ChildThread(int id, CountDownLatch latch){
this.id = id;
this.latch = latch;
}
public void run(){
try{
Thread.sleep(Math.abs(new Random().nextInt(5000)));
System.out.println(String.format("Child Thread %d finished",id));
}catch(InterruptedExcepion e){
e.printStackTrace();
}finally{
latch.countDown();
}
}
}
}
3、使用线程池
public class WithExecutor{
public static void main(String[] args) throws InterruptedExcepion{
ExecutorService pool = Executors.newFixedThreadPool(3);
List<Callable<Void>> list = new ArrayList<Callable<Void>>();
for(int i=0;i<3;i++){
list.add(new ChildThread(i));
}
try{
pool.invokeAll(list);
}finally{
pool.shutdown();
}
System.out.println("Main finished");
}
static class ChildThread implements Callable<Void>{
private int id = -1;
public ChildThread (int id){
this.id = id;
}
public Void call() throws Exception{
try{
Thread.sleep(Math.abs(new Random().nextInt(5000)));
System.out.println(String.format("Child Thread %d finished",id));
}catch(InterruptedException e){
e.printStackTrace();
}
return null;
}
}
}
1.互斥条件(进程独占资源)
2.请求与保持(进程因请求资源而阻塞时,对已获得的资源保持不放)
3.不剥夺条件(进程已获得的资源,在末使用完之前,不能强行剥夺)
4.循环等待(若干进程之间形成一种头尾相接的循环等待资源关系)
由于在平时的工作中,线上服务器是分布式多台部署的,经常会面临解决分布式场景下数据一致性的问题,那么就要利用分布式锁来解决这些问题。
同步和异步最大的区别就在于。一个需要等待,一个不需要等待。同步可以避免出现死锁,读脏数据的发生,一般共享某一资源的时候用,如果每个人都有修改权限,同时修改一个文件,有可能使一个人读取另一个人已经删除的内容,就会出错,同步就会按顺序来修改。
1.先判断线程池中核心线程池所有的线程是否都在执行任务。如果不是,则新创建一个线程执行刚提交的任务,否则,核心线程池中所有的线程都在执行任务,则进入第2步;
2.判断当前阻塞队列是否已满,如果未满,则将提交的任务放置在阻塞队列中;否则,则进入第3步;
3.判断线程池中所有的线程是否都在执行任务,如果没有,则创建一个新的线程来执行任务,否则,则交给饱和策略进行处理
Error表示系统级的错误和程序不必处理的异常,是很难恢复的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况;
Exception表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。
会执行,在方法返回调用者前执行。
异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。
受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。
ArithmeticException(算术异常)
ClassCastException (类转换异常)
IllegalArgumentException (非法参数异常)
IndexOutOfBoundsException (下标越界异常)
NullPointerException (空指针异常)
SecurityException (安全异常)
按功能来分:输入流(input)、输出流(output)。
按类型来分:字节流和字符流。
字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。
BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
1.OSI模型把网络通信的工作分为7层,分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。每一层对于上一层来讲是透明的,上层只需要使用下层提供的接口,并不关心下层是如何实现的。
2.TCP/IP参考模型是首先由ARPANET所使用的网络体系结构。这个体系结构在它的两个主要协议出现以后被称为TCP/IP参考模型(TCP/IP Reference Model)。这一网络协议共分为四层:网络访问层、互联网层、传输层和应用层。
3.TCP/IP模型的分层及与OSI参考模型的对应关系为:
网络访问层–对应OSI参考模型的物理层和数据链路层;
网络层–对应OSI参考模型的网络层;
传输层–对应OSI参考模型的传输层;
应用层–对应OSI参考模型的会话层、表示层和应用层。
在TCP的连接中,数据流必须以正确的顺序送达对方。TCP的可靠性是通过顺序编号和确认(ACK)来实现的。
TCP 连接是通过三次握手进行初始化的。三次握手的目的是同步连接双方的序列号和确认号并交换 TCP 窗口大小信息。
第一次是客户端发起连接;第二次表示服务器收到了客户端的请求;第三次表示客户端收到了服务器的反馈。
TCP(Tranfer Control Protocol)的缩写,是一种面向连接的保证传输的协议,在传输数据流前,双方会先建立一条虚拟的通信道。可以很少差错传输数据。
UDP(User DataGram Protocol)的缩写,是一种无连接的协议,使用UDP传输数据时,每个数据段都是一个独立的信息,包括完整的源地址和目的地,在网络上以任何可能的 路径传到目的地,因此,能否到达目的地,以及到达目的地的时间和内容的完整性都不能保证。
所以TCP比UDP多了建立连接的时间。相对UDP而言,TCP具有更高的安全性和可靠性。
TCP协议传输的大小不限制,一旦连接被建立,双方可以按照一定的格式传输大量的数据,而UDP是一个不可靠的协议,大小有限制,每次不能超过64K。
get从服务器获取信息,post向服务器传信息
get传送数据量比较小,post可以比较大
get安全性比较低
cookie 是 Web 服务器发送给浏览器的一块信息。浏览器会在本地文件中给每一个 Web 服务器存储 cookie。以后浏览器在给特定的 Web 服务器发请求的时候,同时会发送所有为该服务器存储的 cookie。
无论客户端浏览器做怎么样的设置,session都应该能正常工作。客户端可以选择禁用 cookie,但是, session 仍然是能够工作的,因为客户端无法禁用服务端的 session。
JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。
JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。
具体来说 JDK 其实包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具。简单来说:如果你需要运行 java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,需要安装 JDK。
Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的,类装载器所做的工作实质是把类文件从硬盘读取到内存中
java中的类大致分为三种
1).系统类
2).扩展类
3).由程序员自定义的类
类装载方式,有两种
1).隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中
2).显式装载, 通过class.forname()等方法,显式加载需要的类
类加载的动态性体现
一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再
运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现
java类装载器
Java中的类装载器实质上也是类,功能是把类载入jvm中,值得注意的是jvm的类装载器并不是一个,而是三个,层次结构如下:
Bootstrap Loader - 负责加载系统类
- - ExtClassLoader - 负责加载扩展类
- - AppClassLoader - 负责加载应用类
为什么要有三个类加载器,一方面是分工,各自负责各自的区块,另一方面为了实现委托模型,下面会谈到该模型
1).Bootstrap类加载器 – JRE/lib/rt.jar
2).Extension类加载器 – JRE/lib/ext或者java.ext.dirs指向的目录
3).Application类加载器 – CLASSPATH环境变量, 由-classpath或-cp选项定义,或者是JAR中的Manifest的classpath属性定义.
类加载器之间是如何协调工作的
类加载器的工作原理基于三个机制:委托、可见性和单一性
委托机制
当一个类加载和初始化的时候,类仅在有需要加载的时候被加载。假设你有一个应用需要的类叫作Abc.class,首先加载这个类的请求由 Application类加载器委托给它的父类加载器Extension类加载器,然后再委托给Bootstrap类加载器。Bootstrap类加载器 会先看看rt.jar中有没有这个类,因为并没有这个类,所以这个请求由回到Extension类加载器,它会查看jre/lib/ext目录下有没有这 个类,如果这个类被Extension类加载器找到了,那么它将被加载,而Application类加载器不会加载这个类;而如果这个类没有被 Extension类加载器找到,那么再由Application类加载器从classpath中寻找。记住classpath定义的是类文件的加载目 录,而PATH是定义的是可执行程序如javac,java等的执行路径。
可见性机制
可见性的原理是子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。
单一性机制
根据这个机制,父加载器加载过的类不能被子加载器加载第二次。虽然重写违反委托和单一性机制的类加载器是可能的,但这样做并不可取。
类的加载过程
1)装载:查找并加载类的二进制数据;
2)链接:
验证:确保被加载类的正确性;
准备:为类的静态变量分配内存,并将其初始化为默认值;
解析:把类中的符号引用转换为直接引用;
3)初始化:为类的静态变量赋予正确的初始值;
类的初始化
类什么时候才被初始化:
1)创建类的实例,也就是new一个对象
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4)反射(Class.forName(“com.lyj.load”))
5)初始化一个类的子类(会首先初始化子类的父类)
6)JVM启动时标明的启动类,即文件名和类名相同的那个类
类的初始化步骤
1)如果这个类还没有被加载和链接,那先进行加载和链接
2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
3)加入类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句
理论上Java因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是Java被广泛使用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被GC回收,因此也会导致内存泄露的发生。
GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc() 或 Runtime.getRuntime().gc() ,但JVM可以屏蔽掉显示的垃圾回收调用。
垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在Java诞生初期,垃圾回收是Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今Java的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常觉得iOS的系统比Android系统有更好的用户体验,其中一个深层次的原因就在于Android系统中垃圾回收的不可预知性。
不能,虽然你可以调用 System.gc() 或者 Runtime.gc(),但是没有办法保证 GC 的执行。
当通过 Java 命令启动 Java 进程的时候,会为它分配内存。内存的一部分用于创建堆空间,当程序中创建对象的时候,就从空间中分配内存。GC 是 JVM 内部的一个进程,回收无效对象的内存用于将来的分配。
JVM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。
可以通过 java.lang.Runtime 类中与内存相关方法来获取剩余的内存,总内存及最大堆内存。通过这些方法你也可以获取到堆使用的百分比及堆内存的剩余空间。Runtime.freeMemory() 方法返回剩余空间的字节数,Runtime.totalMemory() 方法总内存的字节数,Runtime.maxMemory() 返回最大内存的字节数。
1)方法区(method):被所有的线程共享。方法区包含所有的类信息和静态变量。
2)堆(heap):被所有的线程共享,存放对象实例以及数组,Java堆是GC的主要区域。
3)栈(stack):每个线程包含一个栈区,栈中保存一些局部变量等。
4)程序计数器:是当前线程执行的字节码的行指示器。
持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。所有新生成的对象首先都是放在年轻代的,年老代中存放的都是一些生命周期较长的对象。
内存溢出:程序申请内存时,没有足够的内存,out of memory;
内存泄漏值垃圾对象无法回收,可以使用memory analyzer工具查看泄漏。
所有资源来源于网络收集整理,如有错误欢迎指正,如有更好的题库,也请留言以便持续更新,谢谢!