Java知识点回顾(基础、并发、虚拟机)

一、JAVA基础

———————————————集合——————————————–

JAVA集合分为
set (无序不重复),
list(有序可重复),
map(键值对),
queue(队列)

数组保存的是定长的数据,集合保存的是不确定的数据
数组元素可以基本类型的值和对象,集合只能保存对象

Java的集合类主要由两个接口派生而出:
Collection和Map,Collection和Map是Java集合框架的根接口。

———————————————泛型——————————————

一、泛型的目的
避免在运行时强制类型转换而出现ClassCastException,即类型转换异常。

二、泛型的使用
1.泛型类和泛型接口
public class ArrayList<E> extends AbstractList<E> implements List<E> {
}
public interface List<E> extends Collection<E> {
}
2.泛型的方法
public E getkey() {
        return key;
    }
3.泛型构造器
public class Person {
    public <T> Person(T t) {
        System.out.println(t);
    }
}


三、类型通配符
问号(?)被成为通配符,它的元素类型可以匹配任何类型。
1.上限通配符
它表示集合中的所有元素都是Shape类型或者其子类
List<? extends Shape>
2.下限通配符
它表示集合中的所有元素都是Circle类型或者其父类
List <? super Circle>

———————————————–反射———————————————-

Java反射机制定义
运行状态中,获取任意一个类的所有属性和方法

Java 反射机制的应用场景
1、反编译
2、框架编写

———————————————-注解—————————————–

注解(元数据)的定义:
注解即元数据,元数据是对方法、字段、类的描述

注解的作用:
1、跟踪代码中的依赖性
2、代码编译检查
3、使代码更加简洁

————————————-JAVA IO————————————-

在Java中有输入、输出两种IO流,每种输入、输出流又分为字节流和字符流两大类。

Java中把不同的输入/输出源抽象表述为"流"。
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。

流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

输入时是流从数据源流向程序。
输出时是流从程序传向数据源,
而数据源可以是内存,文件,网络或程序等。

关于字节,每个字节(byte)有8bit(位)组成,每种数据类型又几个字节组成
关于字符,我们可能知道代表一个汉字或者英文字母。

Java采用unicode编码,2个字节来表示一个字符,一个中文或英文字符的unicode编码都占2个字节

输入流:InputStream(字节流)、Reader(字符流)
输出流:OutputStream(字节流)、Writer(字符流)

———————————RandomAccessFile———————————-

RandomAccessFile既可以读取文件内容,也可以向文件输出数据。

同时,RandomAccessFile支持“随机访问”的方式

程序可以直接跳转到文件的任意地方来读写数据。

应用场景:
1、断点续传
2、结合线程池多线程分段下载单个文件

———————————— JAVANIO————————————-

定义:
Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java1.4开始)
Java NIO提供了与标准IO不同的IO工作方式。

标准的IO基于字节流和字符流进行操作的,
而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,
数据总是从通道读取到缓冲区中,或者从缓冲区写入通道也类似。

Java NIO 由以下几个核心部分组成:
Buffer(利用Buffer读写数据)
Channel(通道)和流非常相似
Selector 检查NIO Channel是否处于可读、可写。

—————————————-Java异常—————————————–

Java异常是Java提供的一种识别及响应错误的一致性机制。

Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性

Java异常机制用到的几个关键字:trycatchfinallythrowthrows

————————————– 抽象类和接口———————————-

abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。

接口和抽象类的概念不一样。接口是对动作的抽象,抽象类是对根源的抽象。从设计理念上,接口反映的是 “like-a” 关系,抽象类反映的是 “is-a” 关系。 抽象类表示的是,这个对象是什么。接口表示的是,这个对象能做什么

即:
abstract class (名词),被继承
interface(动词),被实现

————————————深拷贝和浅拷贝———————————–

Java中有三种类型的对象拷贝:
浅拷贝(Shallow Copy)、深拷贝(Deep Copy)、延迟拷贝(Lazy Copy)。

什么是浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。

如果属性是基本类型,拷贝的就是基本类型的值;
如果属性是内存地址(引用类型),拷贝的就是内存地址 ,
因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

什么是深拷贝

深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。
当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。

简单理解一:

在有指针的情况下,浅拷贝只是增加了一个指针指向已经存在的内存,而深拷贝就是增加一个指针并且申请一个新的内存,使这个增加的指针指向这个新的内存,采用深拷贝的情况下,释放内存的时候就不会出现在浅拷贝时重复释放同一内存的错误!

简单理解二 :

浅拷贝 只拷贝指针,深拷贝就是拷贝他的值,重新生成的对像。就像是浅拷贝就是你的影子,深拷贝是你的克隆人,你没了影子也就没了,但是克隆人还活着。

————————————— transient———————————

对象序列号需要实现Serilizable接口
将不需要序列化的属性前添加关键字transient,该属性就不会序列化

拓展:
我们知道在Java中,对象的序列化可以通过实现两种接口来实现,若实现的是Serializable接口,则所有的序列化将会自动进行。

若实现的是Externalizable接口,则没有任何东西可以自动序列化,需要在writeExternal方法中进行手工指定所要序列化的变量,这与是否被transient修饰无关。

因此transient只适用于Serializable接口序列化方式

—————————–Java finally与return执行顺序———————–

总结:
1finally语句在return语句执行之后return返回之前执行的。
2finally块中的return语句会覆盖try块中的return返回。
3、 如果finally语句中没有return语句覆盖返回值,那么原来的返回值可能因为finally里的修改而改变也可能不变。

最后总结:
finally块的语句在trycatch中的return语句执行之后返回之前执行且finally里的修改语句可能影响也可能不影响trycatchreturn已经确定的返回值,若finally里也有return语句则覆盖trycatch中的return语句直接返回。

————————————Java8新特性———————————–

Lambda表达式和函数式接口
接口的默认方法和静态方法
方法引用
重复注解
更好的类型推断
拓宽注解的应用场景

二、JAVA并发

——————————-Java创建线程的三种方式—————————-

1、继承Thread2、实现Runnable接口
3、通过Callable和Future创建线程

创建线程的三种方式的对比

采用实现Runnable、Callable接口的方式创见多线程时,

优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

使用继承Thread类的方式创建多线程时

优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。

——————————————Java线程池———————————

定义:
在Android中,由于主线程的诸多限制,像网络请求等一些耗时的操作我们必须在子线程中运行。

我们往往会通过new Thread来开启一个子线程,待子线程操作完成以后通过Handler切换到主线程中运行。

这么以来我们无法管理我们所创建的子线程,并且无限制的创建子线程,它们相互之间竞争,很有可能由于占用过多资源而导致死机或者OOM。

所以在Java中为我们提供了线程池来管理我们所创建的线程。

线程池的优势

①降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
②提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行;
③方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率;
④更强大的功能,线程池提供了定时、定期以及可控线程数等功能的线程池,使用方便简单。

关键字:ThreadPoolExecutor

四种线程池类
1. newFixedThreadPool(数量固定,都是核心线程)

2.newCachedThreadPool(缓存,核心线程数为0,都是非核心线程)

3. newScheduledThreadPool(固定核心线程,非核心线程无限制)

4. newSingleThreadExecutor(单核心线程,一个一个任务排队等待)

—————————————死锁———————————————

在JAVA编程中,有3种典型的死锁类型:
静态的锁顺序死锁(两个线程,A方法等B方法,B方法等A方法)
动态的锁顺序死锁(两个线程调用同一个方法时,入参颠倒造成的死锁)
协作对象之间发生的死锁。(线程1持有A对象锁并等待B对象锁,线程2持有B对象锁并等待A对象锁。)

避免死锁:
在写代码时,要确保线程在获取多个锁时采用一致的顺序。同时,要避免在持有锁的情况下调用外部方法。

—————————————线程同步—————————————-

Java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时
(如数据的增删改查),将会导致数据不准确,相互之间产生冲突。

加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
一共有两种锁,来实现线程同步问题,分别是:synchronized和ReentrantLock

synchronized实现同步的基础:
Java中每个对象都可以作为锁。当线程试图访问同步代码时,必须先获得对象锁,退出或抛出异常时必须释放锁。

Synchronzied实现同步的表现形式分为:代码块同步 和 方法同步。

ReentrantLock,一个可重入的互斥锁,它具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

————————————–线程间通信的两种方式———————————–

Object类中相关的方法有两个:
notify方法和三个wait方法
拓展:
http://blog.csdn.net/yang_teng_/article/details/53325280

————————————–volatile关键字———————————-

当一个变量定义为 volatile 之后,将具备两种特性:

1.保证此变量对所有的线程的可见性
当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。
但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存

2.禁止指令重排序优化。
有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置)

只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。

更多:
Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。
当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。
volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

  在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。

详见:
https://www.cnblogs.com/zhengbin/p/5654805.html

——————————–乐观锁与悲观锁——————————-

悲观锁,正如其名,它指的是对数据被外界修改持保守态度(悲观),因此,在整个数据处理过程中,将数据处于锁定状态。 

悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)

优点与不足

悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。
但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;
另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。

优点与不足

乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。


详见:
http://www.hollischuang.com/archives/934

——————————-AbstractQueuedSynchronizer—————————-

AQS用来实现锁或其他同步组件的基础框架(注意区别synchronized是在字节码上加指令方式,通过底层机器语言保证同步)。

AQS使用int类型的volatile变量维护同步状态(state),使用Node实现FIFO队列来完成线程的排队执行。在锁的实现中通过组合AQS对象的方式使用,利用AQS实现锁的语义。

AQS与锁(如Lock)的对比:

锁是面向使用者的,锁定义了用户调用的接口,隐藏了实现细节;

AQS是锁的实现者,通过用AQS简化了锁的实现屏蔽了同步状态管理,线程的排队,等待唤醒的底层操作。

简而言之,锁是面向使用者,AQS是锁的具体实现者。

——————————-深入理解ReentrantLock—————————-

重入锁(ReentrantLock)是一种递归无阻塞的同步机制

http://blog.csdn.net/yanyan19880509/article/details/52345422

———————————Java并发集合——————————–

Java并发集合——ArrayBlockingQueue ,LinkedBlockingQueue,ConcurrentHashMap
http://blog.csdn.net/u013277740/article/details/79367237

三、JAVA虚拟机

本部分内容是关于Java虚拟机的一些面试高频知识点的总结。说到对Java虚拟机的学习,就不得不提下这本书《深入理解Java虚拟机》。

本部分的内容也是基于这本书进行整理的,这本书基本是面试必备。+

关于Java虚拟机,重点考察以下三个方面的内容:

内存区域/内存模型
类加载机制
垃圾收集算法/收集器

目录

  • 对象的创建、内存布局和访问定位
  • Java内存区域与内存模型
  • Java类加载机制及类加载器详解
  • JVM中垃圾收集算法及垃圾收集器详解
  • JVM怎么判断对象是否已死?

    ———————对象的创建、内存布局和访问定位——————-

一、对象的创建

,虚拟机遇到一个new指令时,
1、检查指令在常量池中代表的类是否已经被加载,解析和初始化过,如果没有,那必须先执行响应的类加载过程;

2.在类加载检查功通过后,为新生对象分配内存。对象所需的内存大小在类加载完成后便可完全确定。

二、对象的内存布局

分为3个区域:
对象头
实例数据(存储的有效信息)
对齐填充

三、对象的访问定位

Java程序需要通过栈上了reference数据来操作堆上的具体对象。
目前主流的访问方式有两种

句柄(句柄池)
直接指针(对象地址)

——————————-Java内存区域与内存模型 —————————

Java内存区域

方法区(公有):被虚拟机加载的类信息,常量,静态常量,即编译后的代码等数据。

堆(公有): 存放实例对象,Java堆是垃圾收集器管理的主要区域,因此很多时候也被称为“GC堆”。

虚拟机栈(线程私有): java方法执行时创建的一个栈帧,每一个方法从调用直至完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

本地方法栈(线程私有): 与虚拟机栈所发挥的作用相似,
虚拟机栈为虚拟机执行java方法,而本地方法栈为虚拟机使用到的Native方法服务。

程序计数器(线程私有): 一块较小的内存,当前线程所执行的字节码的行号指示器。
字节码解释器工作时,就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。


Java内存模型

Java内存模型的目的: 让java程序在各种平台下都能达到一致的内存访问效果。

主要目标: 定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。

Java内存模型规定了所有的变量都存储在主内存中。
每条线程中还有自己的工作内存,线程的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷贝而来)。

线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

—————————Java类加载机制及类加载器 ————————-

一、类加载机制
把类从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。

在Java语言里,类型的加载、连接和初始化过程都是在程序运行期间完成的

二、类加载器
类加载机制中加载部分的功能是将类的class文件读入内存,并为之创建一个java.lang.Class对象。
这部分功能就是由类加载器来实现的。

——————————-JVM中垃圾收集 ————————–

一、垃圾收集算法

1.标记-清除算法
①首先标记出所有需要回收的对象
②在标记完成后统一回收所有被标记的对象。

2.复制算法
将可用内存按容量大小划分为大小相等的两块,每次只使用其中的一块。当一块内存使用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉

3.标记-整理算法
复制收集算法在对象存活率较高时,就要进行较多的复制操作,效率就会变低。 根据老年代的特点,提出了“标记-整理”算法。
标记过程仍然与”标记-清除“算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。

4.分代收集算法
一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法。
在老年代中,因为对象存活率高、没有额外空间对它进行分配担保,就必须采用“标记-清除”或“标记-整理”算法来进行回收。

二、垃圾回收机制的一些知识
1.JVM中的年代
JVM中分为年轻代(Young generation)和老年代(Tenured generation)。
HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫fromto)。默认比例为81

一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中

2.Minor GC和Full GC的区别

Minor GC:指发生在新生代的垃圾收集动作,该动作非常频繁。
Full GC/Major GC:指发生在老年代的垃圾收集动作,出现了Major GC,经常会伴随至少一次的Minor GC。Major GC的速度一般会比Minor GC慢10倍以上。

3. 空间分配担保
在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,
如果这个条件成立,那么Minor GC可以 确保是安全的。

如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。
如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,

如果大于,则将尝试进行一次Minor GC,尽管这个Minor GC是有风险的。
如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。

三、垃圾收集器
1.Serial收集器(它在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。)
2.ParNew收集器(Serial收集器的多线程版本)
3.Parallel Scavenge收集器(使用复制算法,又是并行的多线程收集器)
4.Serial Old收集器(是Serial收集器的老年代版本,同样是一个单线程收集器,使用“标记-整理”算法。这个收集器的主要意义也是在于给Client模式下虚拟机使用。)
5.Parallel Old收集器(是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法)
6.CMS(Concurrent Mark Sweep)收集器(是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。)
7.G1收集器(是当今收集器技术发展的最前沿成果之一。是一款面向服务端应用的垃圾收集器)

—————————JVM怎么判断对象是否已死? ———————–

一、引用计数法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能被再使用的。

主流的JVM里面没有选用引用计数算法来管理内存,其中最主要的原因是它很难解决对象间的互循环引用的问题。

二、可达性分析算法
通过一些列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
即:
方法运行时,方法中引用的对象;类的静态变量引用的对象;类中常量引用的对象;Native方法中引用的对象

在可达性分析算法中,要真正宣告一个对象死亡,至少要经历两次标记过程:

三、判断对象是否存活与“引用”有关

在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为
强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种,这四种引用强度依次逐渐减弱。

强引用: 普遍存在的,类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

软引用: 有用但非必须的对象。在系统将要发生内存溢出异常之前,将会把这些对象进行第二次回收。

弱引用: 非必须对象。被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

虚引用: 一个对象是否有虚引用存在,为一个对象设置虚引用的唯一目的就是能在这个对象被收集器回收时刻得到一个系统通知。
发布了55 篇原创文章 · 获赞 93 · 访问量 33万+

猜你喜欢

转载自blog.csdn.net/zx_android/article/details/79376725