@hainingwyx
2018-05-22T09:26:33.000000Z
字数 23587
阅读 1264
Java
https://blog.csdn.net/justloveyou_/article/details/78653660
https://blog.csdn.net/scythe666/article/details/51841161
https://blog.csdn.net/justloveyou_/article/details/64127789
https://my.oschina.net/clopopo/blog/149368
http://www.hollischuang.com/archives/2333
面向过程就是分析出解决问题所需要的步骤 ,然后用函数一一实现 ,按照顺序一一调用;面向对象是把构成事务分解成各个对象的行为,建立对象不是为了完成步骤,而是为了描述某个事务在整个问题的解决过程中的行为。通过指挥对象的各个行为,完成事务。
java解释性语言,c/c++编译型;
java程序执行是java编译器编译成字节码文件,JVM解释执行;C/C++编译链接后生成可执行二进制程序;
java纯面向对象,除了基本数据类型都是类,必须在类中实现;java没有指针;提供了垃圾回收器进行垃圾回收;
java不支持多继承;平台无关,每种数据类型分配固定的长度;JAVA 里的块用大括号对包括,Python 以冒号 + 四个空格缩进表示。
JAVA 的类型要声明,Python 的类型不需要。
JAVA 基本上是类/结构操作,也就是面向对象处理,Python 可以以独立的函数模块来处理逻辑而不需要放到类中。
JAVA 每行语句以分号结束,Python 可以不写分号。
JAVA 中的字符串以双引号括起来,Python 中单引号或双引号都可以。
Python的垃圾收集机制主要使用的引用计数方式.
静态类型指的是编译器在compile 动态类型指的是编译器在运行时执行类型检查。在声明了一个变量之后,不能改变它的类型的语言,是静态语言;能够随时改变它的类型的语言,是动态语言。Python是动态的,Java是静态的。
Gosling认为,多继承是一种很少使用,并且很容易混淆的特性,所以Java语言就像删除操作符重载特性一样删除了多继承这种特性。
1)避免diamond problem(菱形问题),假设class A有一个foo()方法,class B和Class C都继承了A并实现了foo()方法,Class D 同时继承了Class B、Class C,如果在D中调用foo()方法,那哪个会被调用呢?这个被称为diamond problem,因为这种继承结构与四个边的菱形类似
2)多重继承复杂化了设计并在类型转换、构造上带来了更多的问题,并且也没有很多必须使用多重继承的场景,因此为了简单化而不使用多重继承是明智的。为了避免歧义,Java使用interface实现了single inheritance,interface只提供了方法的声明而没有方法的实现,因此在子类中只有一个方法的实现也就不会产生歧义了。Java8里面默认方法,面对多继承时,需要overide,否则会报错。
间接实现多继承:inner class可以说是多重继承问题的完整解决方案。 一个类不能继承自多个一般类。但我们可以让其内部的多个inner class各自继承某一实现类达到类似的目的。
多态的表现形式。重载是一个类中的多态,一个类中定义多个同名方法,有着不同的形参列表。重写是子类覆盖父类方法,函数名和形参列表相同。
+ 重载是同一类中方法关系;重写是子类和父类方法的关系
+ 重写形参列表相同,重载不同;
+ 重写是根据调用对象决定调用方法,重载根据形参和实参列表决定调用方法
+ 编译时多态;运行时多态。
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须改 | 一定不能改 |
返回类型 | 可以改 | 一定不能改 |
异常 | 可以改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问 | 可以改 | 一定不能做更严格的限制(可以降低限制) |
可变不可变、安全不安全两个方面:
1.StringBuffer(同步的)和String(不可变的)都是线程安全的,StringBuilder是线程不安全的;
2.String是不可变的,StringBuilder和StringBuffer是可变的;
3.String的连接操作的底层是由StringBuilder实现的;
4.三者都是final的,不允许被继承;
5.StringBuilder 以及 StringBuffer 都是抽象类AbstractStringBuilder的子类,它们的接口是相同的。
1). 不可变类String的原因
String主要的成员变量 char value[]是private,final的,并且没有对应的 getter/setter;
String 对象一旦初始化完成,char value[]就不可修改;并且其所提供的接口任何对这些域的修改都将返回一个新对象;
大致思路是jdk7和jdk8的实现原理及区别(重点有实现的数据结构,存储单元从Entry到Node的转变,加载因子,什么时候扩容,jdk1.8扩容的具体实现方式等等),HashMap和HashTable的区别,HahsMap和HashSet的关系。要结合源码说。
(1).实现模板不同:虽然二者都实现了Map接口,但HashTable继承于Dictionary类,而HashMap继承于AbstractMap。Dictionary是可将任何键映射到相应值的类的抽象父类,而AbstractMap是基于Map接口的骨干实现,它以最大限度地减少实现此接口所需的工作。
(2). 对键值的限制不同:HashMap可以允许存在一个为null的key和任意个为null的value,HashTable中的key和value都不允许为null。
(3). 线程安全性不同:Hashtable的方法是同步的,实现线程安全的Map;而HashMap的方法不是同步的,是Map的非线程安全实现。
(4). 地位不同:在并发环境下,Hashtable虽然是线程安全的,但是我们一般不推荐使用它,因为有比它更高效、更好的选择ConcurrentHashMap;而单线程环境下,HashMap拥有比Hashtable更高的效率,所以更没必要选择它了。
以数组实现。节约空间,但数组有容量限制。超出限制时会增加50%容量,用System.arraycopy()复制到新的数组,因此最好能给出数组大小的预估值。默认第一次插入元素时创建大小为10的数组。当增加数据的时候,如果ArrayList的大小已经不满足需求时,那么就将数组变为原长度的1.5倍,之后的操作就是把老的数组拷到新的数组里面。Array的put和get函数就比较简单了,先做index检查,然后执行赋值或访问操作。
接口中可以含有 变量和方法。但是要注意,接口中的变量会被隐式地指定为
public static final
变量(并且只能是public static final
变量,用private
修饰会报编译错误),而方法会被隐式地指定为public abstract
方法且只能是public abstract
方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。如果一个非抽象类遵循了某个接口,就必须实现该接口中的所有方法。对于遵循某个接口的抽象类,可以不实现该接口中的抽象方法。
抽象类定义为“包含抽象方法的类”,如果一个类不包含抽象方法,只是用abstract修饰的话也是抽象类。 包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。
语法层面上的区别
1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
设计层面上的区别
1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。
2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。
序列化(Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。之后可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
java中的序列化(serialization)机制能够将一个实例对象的状态信息写入到一个字节流中,使其可以通过socket进行传输、或者持久化存储到数据库或文件系统中;然后在需要的时候,可以根据字节流中的信息来重构一个相同的对象。序列化机制在java中有着广泛的应用,EJB、RMI等技术都是以此为基础的。
一般而言,要使得一个类可以序列化,只需简单实现java.io.Serializable接口即可(还要实现无参数的构造方法)。该接口是一个标记式接口,它本身不包含任何内容,实现了该接口则表示这个类准备支持序列化的功能。
序列化一般有三种形式:默认形式、xml、json格式
三种情况下需要进行序列化:
1、把对象持久化到文件或数据中
2、在网络上传输
3、进行RMI传输对象时
RPC和RMI都是远程调用,属于中间件技术。RMI是针对于java语言的,它使用的是JRMP协议通信,而RPC是更大众化的,使用http协议传输。
Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。
常用序列化技术有3种:java seriaizable,hessian,hessian2,以及protobuf
ConcurrentHashMap不同于HashMap,它既不允许key值为null,也不允许value值为null。否则会抛出空指针异常。
并发环境下为什么使用ConcurrentHashMap
- HashMap在高并发的环境下,执行put操作会导致HashMap的Entry链表形成环形数据结构,从而导致Entry的next节点始终不为空,因此产生死循环。
- HashTable虽然是线程安全的,但是效率低下,当一个线程访问HashTable的同步方法时,其他线程如果也访问HashTable的同步方法,那么会进入阻塞或者轮询状态。
- 在jdk1.6中ConcurrentHashMap使用锁分段技术提高并发访问效率。首先将数据分成一段一段地存储,然后给每一段数据配一个锁,当一个线程占用锁访问其中一段数据时,其他段的数据也能被其他线程访问。然而在jdk1.8中的实现已经抛弃了Segment分段锁机制,利用CAS+Synchronized来保证并发更新的安全,底层依然采用数组+链表+红黑树的存储结构。
线程的工作内存中保存了被该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。
如果一个变量是volatile修饰的,Java内存模型会在写入这个字段之后插进一个Write-Barrier指令,并在读这个字段之前插入一个Read-Barrier指令。
这意味着,如果写入一个volatile变量a,可以保证:
1、不同线程对共享变量进行操作时的可见性,即一个线程修改了某个变量的值,这个新值对其他线程来说是立即可见的;
2、禁止进行指令重排序。
因为 volatile 关键字无法保证操作的原子性。通常来说,使用 volatile 必须具备以下两个条件:
1)对变量的写操作不依赖于当前值;
2)该变量没有包含在具有其他变量的不变式中。
volatile 主要使用的场合是:
在多线程环境下及时感知共享变量的修改,并使得其他线程可以立即得到变量的最新值。
主要相同点:lock能完成synchronized所实现的所有功能
主要不同点:lock有比synchronized更精确的线程语义和更好的性能.synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放.
1、ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候。
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,如果使用 synchronized,如果A不释放,B将一直等下去,不能被中断 如果使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情
ReentrantLock
获取锁定三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
2、synchronized
是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized
的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock
则不行,lock
是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
Synchronized
3、在资源竞争不是很激烈的情况下,的性能要优于
ReetrantLock,但是在资源竞争很激烈的情况下,
Synchronized的性能会下降几十倍,但是
ReetrantLock`的性能能维持常态;
- 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
- 一个线程持有锁会导致其它所有需要此锁的线程挂起。
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。
乐观锁其实是一种思想。相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。
主要步骤:冲突检测和数据更新。
实现:CAS(Compare And Swap)
CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”这其实和乐观锁的冲突检查+数据更新的原理是一样的。
CAS会导致“ABA问题”。
CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。
部分乐观锁的实现是通过版本号(version)的方式来解决ABA问题,乐观锁每次在执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行+1操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现ABA问题,因为版本号只会增加不会减少
在JVM创建对象的过程中。对象创建在虚拟机中是非常频繁的。即使是仅仅修改一个指针所指向的位置,在并发情况下也不是线程安全的,可能正在给对象A分配内存空间,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。解决这个问题的方案有两种,其中一种就是采用CAS配上失败重试的方式保证更新操作的原子性。
Java内存模型 规定所有的变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作,并且每个线程不能访问其他线程的工作内存。
原子性
Java内存模型只保证了基本数据类型的读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized
和Lock
来实现。
可见性
当一个共享变量被volatile
修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
通过synchronized
和Lock
也能够保证可见性,synchronized
和Lock
能保证同一时刻只有一个线程获取锁然后执行同步代码,并且 在释放锁之前会将对变量的修改刷新到主存当中,因此可以保证可见性。
有序性
在 Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
开放定址法:当出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi,将相应元素存入其中。这种方法有一个通用的再散列函数形式:
其中H(key)为哈希函数,m为表长,称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。
再哈希法
同时构造多个不同的哈希函数:
当哈希地址发生冲突时,再计算……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。
链地址法
将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。
建立公共溢出区
将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。
ThreadLocal 不是用于解决共享变量的问题的,不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制。
(1). Lock是一个接口,是JDK层面的实现;而synchronized是Java中的关键字,是Java的内置特性,是JVM层面的实现;
(2). synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
(3). Lock 可以让等待锁的线程响应中断,而使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
(4). 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到;
(5). Lock可以提高多个线程进行读操作的效率。
(6). 当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。
线程隔离:虚拟机栈、本地方法栈、程序计数器
线程共享:方法区、堆
程序计数器:若正在执行的是java方法,则计数器记录的是正在执行的字节码指令的地址,若正在执行的是native方法,则计数器为空。
虚拟机栈:描述的是Java方法执行的内存模型:每个方法都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。局部变量表存储各种基本数据类型、对象引用类型,局部变量表的内存空间在编译器确定,在运行期不变。如果请求栈深度大于虚拟机所允许的最大值,抛出StackOverflowError。对于大部分可动态扩展栈的虚拟机,如果申请不到足够的内存,就抛出StackOfMemoryError。
本地方法栈:为虚拟机的Native方法服务。
方法区:用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。常量池属于方法区。
Java堆:java堆是被所有线程共享的内存区域,在虚拟机启动时创建,用来分配对象实例和数组。堆是垃圾回收器主要管理的区域,堆可分为新生代和老年代。从内存分配角度看,堆可划分出多个线程私有的分配缓冲区(TLAB)。大小可通过 -Xmx 和 -Xms 控制
对于new指令,
1. 检查能否在常量池中定位到该类的符号引用,检查符号引用代表的类是否已被加载、解析和初始化。如果没有,执行相应的类加载过程。
2. 类加载检查通过后,为新生对象分配内存。分配内存可以采取a、CAS配上失败重试的方式保证更新操作的原子性;b、把内存分配的动作按照线程划分在不同的空间进行,即本地线程分配缓冲(TLAB)
3. 将分配到的内存空间初始化为零值
4. 对对象进行必要的设置:例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄信息。
5. init方法,按照程序员意愿初始化
虚拟机是一种抽象化的计算机,用来把Java编译生成的中间代码转换为机器可以识别的编码并运行。Java运行时环境(JRE)是Java虚拟机和Java SE API子集。Java开发工具包(JDK)是完整的Java软件开发包,包含了Java虚拟机和Java API类库和Java程序设计语言。
- 涉及内容:引用计数法和可达性分析法、gc roots 类型、引用类型、两次标记过程、垃圾回收算法、内存分配策略、触发垃圾回收、垃圾回收器、也会回收方法区。
回收程序中不再使用的内存。要进行垃圾回收,首先要判断对象是否存活,引出了两个方法:- 引用计数法
- 思想:给对象设置引用计数器,没引用该对象一次,计数器就+1,引用失效时,计数器就-1,当任意时候引用计数器的值都为0时,则该对象可被回收
Java不适用原因:无法解决对象互相循环引用的问题- 可达性分析法
- 以GC Roots为起点,从这些起点开始向下搜索,经过的路径称为引用链。若一个对象到GC Roots之间没有任何引用链,则该对象是不可达的。
那么可作为GC Roots的对象有- 虚拟机栈(栈帧中的局部变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(Native方法)引用的对象
- 在可达性分析过程中,对象引用类型会对对象的生命周期产生影响,JAVA中有这几种类型的引用:
- 强引用:只要该引用还有效,GC就不会回收
- 软引用:内存空间足够时不进行回收,在内存溢出发生前进行回收、用SoftReference类实现
- 弱引用:弱引用关联的对象只能存活到下一次Gc收集、用WeakReference类实现
- 虚引用:无法通过虚引用获得对象实例,也不会对对象的生存时间产生影响、唯一目的:当该对象被Gc收集时,收到一个系统通知。用PhantomReference类实现
- 一个对象真正不可用,要经历两次标记过程:
- 首先进行可达性分析,筛选出与GC Roots没用引用链的对象,进行第一次标记
- 第一次标记后,再进行一次筛选,筛选条件是是否有必要执行finalize()方法。若对象有没有重写finalize()方法,或者finalize()是否已被jvm调用过,则没必要执行,GC会回收该对象
- 若有必要执行,则该对象会被放入F-Queue中,由jvm开启一个低优先级的线程去执行它(但不一定等待finalize执行完毕)。
- finalize()是对象最后一次自救的机会,若对象在finalize()中重新加入到引用链中,则它会被移出要回收的对象的集合。其他对象则会被第二次标记,进行回收
- 垃圾回收算法
- 分代收集算法:根据对象存活周期不同将堆划分新生代和老年代。新生代,每次垃圾收集时都发现有大批对象死去,只有少量存活,选用复制算法--将内存划分为一块较大的Eden空间和两块较小的Survivor空间,每次Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活的对象一次性地复制到另外一块Survivor上,最后清理掉Eden和刚才用过的Survivor。老年代存活率高、没有额外空间对其进行分配担保,使用“标记清理”or"标记整理"。
- 复制算法:把内存分成大小相同的两块,当一块的内存用完了,就把可用对象复制到另一块上,将使用过的一块一次性清理掉。缺点:浪费了一半内存
- 标记清理:标记处要回收的对象,在标记完成后统一回收所有被标记的对象。不足:标记清除的效率不高;易产生大量不连续的内存碎片。缺点:两个阶段的效率都不高;容易产生大量的内存碎片
- 标记整理:标记后所有存活对象向一段移动,清除掉端边界以外的内存。
- 触发GC又涉及到了内存分配规则: (对象主要分配在Eden,若启动了本地线程分配缓冲,将优先在TLAB上分配)
- 对象优先在Eden分配。当Eden区没有足够的空间时就会发起一次Minor GC
- 大对象直接进入老年代。典型的大对象是很长的字符串和数组
- 长期存活的对象进入老年代。 每个对象有年龄计数器,每经过一次GC,计数器值加一,当到达一定程度时(默认15),就会进入老年代;年龄的阈值可通过参数 -XX:MaxTenuringThreshold设置
- 对象年龄的判定。Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象就可直接进入老年代,无须等到MaxTenuringThreshold要求的年龄
- 空间分配担保。发生Minor GC前,jvm会检查老年代最大可用的连续空间是否大于新生代所有对象总空间,若大于,则Minor GC是安全的;若不大于,jvm会查看HandlePromotionFailure是否允许担保失败,若不允许,则改为一次Full GC;若允许担保失败,则检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,若大于,则尝试进行Minor GC;若小于,则要改为Full GC
- 垃圾回收器
新生代:Serial ParNew Parallel Scavenge
老年代:CMS Sreial Old Parallel Old
- Serial
特性:单线程,stop the world,采用复制算法
应用场景:jvm在Client模式下默认的新生代收集器
优点:简单高效。- ParNew
特点:是Serial的多线程版本,采用复制算法
应用场景:在Server模式下常用的新生代收集器,可与CMS配合工作- Parallel Scavenge
特点:并行的多线程收集器,采用复制算法,吞吐量优先,有自适应调节策略
应用场景:需要吞吐量大的时候- Serial Old
特点:Serial的老年代版本,单线程,使用标记-整理算法- Parallel Old
Parallel Scavenge的老年代版本,多线程,标记-整理算法- CMS
特点:以最短回收停顿时间为目标,使用标记-清除算法
过程:
初始标记:stop the world 标记GC Roots能直接关联到的对象
并发标记:进行GC Roots Tracing
重新标记:stop the world;修正并发标记期间因用户程序继续运作而导致标记产生变动的 那一部分对象的标记记录
并发清除:清除对象
优点:并发收集,低停顿
缺点:
对CPU资源敏感
无法处理浮动垃圾(并发清除 时,用户线程仍在运行,此时产生的垃圾为浮动垃圾)
产生大量的空间碎片- G1
特点:面向服务端应用,将整个堆划分为大小相同的region。
并行与并发
分代收集
空间整合:从整体看是基于“标记-整理”的,从局部(两个region之间)看是基于“复制”的。
可预测的停顿:使用者可明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
执行过程:
初始标记:stop the world 标记GC Roots能直接关联到的对象
并发标记:可达性分析
最终标记:修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录
筛选回收:筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划- GC自适应调节策略 Parallel Scavenge收集器有一个参数-XX:+UseAdaptiveSizePolicy。当这个参数打开之后,就不需要手工指定新生代的大小、Eden与Survivor区的比例、晋升老年代对象年龄等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。
- 最后提一下也会回收方法区:
- 永久代中主要回收两部分内容:废弃常量和无用的类
- 废弃常量回收和对象的回收类似
- 无用的类需满足3个条件
该类的所有实例对象已被回收
加载该类的ClassLoader已被回收
该类的Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
- 启动(Bootstrap)类加载器:引导类加载器是用 本地代码实现的类加载器,它负责将 /lib下面的核心类库 或 -Xbootclasspath选项指定的jar包等虚拟机识别的类库 加载到内存中。
- 扩展(Extension)类加载器:扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的,它负责将 /lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库 加载到内存中。
- 系统(System)类加载器:系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,它负责将 用户类路径(java -classpath或-Djava.class.path变量所指的目录,即当前类所在路径及其引用的第三方类库的路径)下的类库 加载到内存中。
JVM在加载类时默认采用的是双亲委派机制。某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归 (本质上就是loadClass函数的递归调用)。因此,所有的加载请求最终都应该传送到顶层的启动类加载器中。如果父类加载器可以完成这个类加载请求,就成功返回;只有当父类加载器无法完成此加载请求时,子加载器才会尝试自己去加载。
注意:由于启动类加载器无法被Java程序直接引用,因此JVM默认直接使用 null 代表启动类加载器。
通过用户自定义的类装载器,程序可加载在编译时尚未存在的类或者接口,并动态连接它们,进行有选择的解析。
1、反射 (调用java.lang.Class.forName(…)加载类)
。Class.forName的一个很常见的用法就是在加载数据库驱动的时候。
2、用户自定义类加载器(包括标准扩展类加载器和系统类加载器)
在Java中,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中,一个类用其 全名 和 一个ClassLoader的实例 作为唯一标识,不同类加载器加载的类将被置于不同的命名空间。
默认会使用调用类的类加载器来进行类加载。
Java类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using) 和 卸载(Unloading)七个阶段。
1) 遇到new、getstatic、putstatic或invokestatic这四条字节码指令如果类没有进行过初始化,则需要先对其进行初始化。
2) 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
3) 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
4) 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
5) 当使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化。
被动引用不会初始化:
- TCP 是面向连接的保证可靠传输的协议 ,UDP是一个非连接协议,发送数据之前不需要建立连接,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的;
- TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
- 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
- TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
- UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
- TCP首部开销20字节;UDP的首部开销小,只有8个字节
- TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
- TCP 保证数据正确性 ,UDP发送方所发送的数据报并不一定以相同的次序到达接收方;
应用:
1,远程连接(Telnet)和文件传输(FTP)都需要不定长度的数据被可靠地传输。但是可靠的传输是要付出代价的,对数据内容正确性的检验必然占用计算机的处理时间和网络的带宽。
2,UDP通常用于局域网高可靠性的分散系统中client/server应用程序。例如视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就可以了,这种情况下显然使用UDP会更合理一些。
(1)首先,Client端发送连接请求SYN=1,ACK=0的报文(SYN=1,seq=client_isn)
(2)Server段接受连接后回复SYN=1,ACK=1的报文,并为这次连接分配资源。(SYN=1,seq=client_isn,ack = client_isn+1)
(3)Client端接收到ACK报文后也向Server段发生SYN=0,ACK=1的报文,并分配资源,这样TCP连接就建立了。(SYN=0,seq=client_isn+1,ack = server_isn+1)
(1)假设Client端发起中断连接请求,也就是发送FIN报文。
(2) Server端接到FIN报文后, Server 端会先发送ACK,告诉Client端,你的请求我收到了,但是我还没准备好,请继续你等我的消息”。
这个时候Client端就进入 FIN_WAIT 状态,继续等待Server端的FIN报文。
(3)当Server端确定数据已发送完成,则向Client端发送FIN报文。
(4)Client端收到FIN报文后,发送 ACK 后进入 TIME_WAIT 状态。
Client端等待了2MSL(最大报文段生存时间)后依然没有收到回复,则证明Server端已正常关闭,那好,我Client端也可以关闭连接了。Ok,TCP连接就这样关闭了!
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,”你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
假象网络是不可靠的,有可能最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。
- 根据目标Ip地址借助ARP请求和ARP响应来确定目标的MAC地址
- 原理:通过广播发送ARP请求,Ip地址一致的主机接收该请求,然后将自己的MAC地址加入的ARP响应包返回给源主机。要进行MAC地址缓存来避免占用网络流量
- 每个主机都会在自己的ARP缓冲区建立一个ARP列表,表示Ip地址和MAC地址的对应关系
- 当源主机要发送数据时,首先检查ARP列表中是否有对应Ip地址的目标主机的MAC地址,若有,则直接发送,若无,则向本网段的所有主机发送ARP数据包,该数据包包括的内容有:源Ip地址,源MAC地址,目标主机Ip地址
- 当本网络的所有主机收到该ARP数据包时,首先检查数据包中的Ip地址是否是自己的Ip地址,若不是则丢弃,若是,则首先从数据包中取出源主机的IP和MAC地址写入自己的ARP列表中,若已存在,则覆盖;然后将自己的MAC地址写入ARP响应包中,告诉源主机自己的MAC地址
- 源主机收到ARP响应包后,将目标主机的IP和MAC地址写入ARP列表中。若源主机一直没有收到ARP响应,则ARP查询失败
- (广播发送ARP请求,单播发送ARP响应) 此时,自己可以加上ARP攻击及免费ARP相关知识点(可自行搜索)
HTTP协议是无状态的,同一个客户端的这次请求和上次请求是没有对应关系,对http服务器来说,它并不知道这两个请求来自同一个客户端。 为了解决这个问题, Web程序引入了Cookie机制来维护状态。
Http响应
在接收和解释请求消息后,服务器返回一个HTTP响应消息。
HTTP响应也是由三个部分组成,分别是:状态行、消息报头、响应正文
HTTP-Version Status-Code Reason-Phrase CRLF
其中,HTTP-Version表示服务器HTTP协议的版本;Status-Code表示服务器发回的响应状态代码;Reason-Phrase表示状态代码的文本描述。
状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:
1xx:指示信息--表示请求已接收,继续处理
2xx:成功--表示请求已被成功接收、理解、接受
3xx:重定向--要完成请求必须进行更进一步的操作
4xx:客户端错误--请求有语法错误或请求无法实现
5xx:服务器端错误--服务器未能实现合法的请求
常见状态代码、状态描述、说明:
200 OK //客户端请求成功
400 Bad Request //客户端请求有语法错误,不能被服务器所理解
401 Unauthorized //请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
403 Forbidden //服务器收到请求,但是拒绝提供服务
404 Not Found //请求资源不存在,eg:输入了错误的URL
500 Internal Server Error //服务器发生不可预期的错误
503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后可能恢复正常
eg:HTTP/1.1 200 OK (CRLF)
冒泡排序(稳定):通过比较两两相邻的数字实现实现较大的数字往一个方向移动,对整个数组的一次冒泡,能将最大的数挑出来,然后对整个数组进行一个循环
复杂度: ,最好
选择排序(稳定):它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
复杂度:
插入排序(稳定):每步将一个待排序的纪录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。每次处理就是将无序数列的第一个元素与有序数列的元素从后往前逐个进行比较,找出插入位置,将该元素插入到有序数列的合适位置中。
复杂度: ,最好O(n)
归并排序(稳定):将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间/有序。用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。
复杂度: 最差相同
快速排序:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
优化:1、优化选取枢纽,三数取中/九数取中
复杂度:
希尔排序:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2 复杂度:
堆排序:堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。 1. 建堆,建堆是不断调整堆的过程,从len/2处开始调整,一直到第一个节点,此处len是堆中元素的个数。 2. 调整堆:调整堆在构建堆的过程中会用到,而且在堆排序过程中也会用到。 3. 堆排序:交换栈顶元素和最后一个元素的位置
复杂度: 最差相同
总结:堆排序、归并算法复杂度较低。稳定性:冒泡、选择、插入、归并。
- 单一职责原则:一个类只有一个引起它变化的原因。
- 开放封闭原则:软件实体(类模块、函数等)可以扩展,但是不可修改。扩展开放,修改封闭。
- 依赖倒转原则:高层模块不应该依赖底层模块,两个都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象,声明方法的返回值、形参,尽可能使用抽象类型。
- 里氏替换原则:子类型必须能够替换掉它们的父类型。
- 迪米特原则:最小知识原则,如果两个类不必彼此通信,那么这两个类就不应当发生直接的相互作用。如果一个类需要调用另一个类的某一个方法,可以通过第三者转发这个调用。
- 接口隔离:接口小而专,高度内聚。
特点
私有的构造方法;
指向自己实例的私有静态引用;
以自己实例为返回值的静态的公有方法。
优点
+ 在内存中只有一个对象,节省内存空间;
+ 避免频繁的创建销毁对象,可以提高性能;
+ 避免对共享资源的多重占用,简化访问;
+ 为整个系统提供一个全局访问点。
由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例,线程每次都只能拿到这个唯一的对象。因此,饿汉式单例天生就是线程安全的。
// 饿汉式单例
public class Singleton1 {
// 指向自己实例的私有静态引用,主动创建
private static Singleton1 singleton1 = new Singleton1();
// 私有的构造方法
private Singleton1(){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton1 getSingleton1(){
return singleton1;
}
}
会有多个线程同时进入 if (singleton2 == null) {…}
语句块的情形发生。该单例类就会创建出多个实例。因此,传统的懒汉式单例是非线程安全的。
// 懒汉式单例
public class Singleton2 {
// 指向自己实例的私有静态引用
private static Singleton2 singleton2;
// 私有的构造方法
private Singleton2(){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton2 getSingleton2(){
// 被动创建,在真正需要使用时才去创建
if (singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}
线程安全的懒汉模式--同步延迟加载synchronized方法
运行效率较低,同步块的作用域较大,锁的粒度较粗。
// 线程安全的懒汉式单例
public class Singleton2 {
private static Singleton2 singleton2;
private Singleton2(){}
// 使用 synchronized 修饰,临界资源的同步互斥访问
public static synchronized Singleton2 getSingleton2(){
if (singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}
同步延迟加载 — synchronized块
运行效率较低和使用synchronized方法的版本相比,基本没有任何效率上的提高。
// 线程安全的懒汉式单例
public class Singleton2 {
private static Singleton2 singleton2;
private Singleton2(){}
public static Singleton2 getSingleton2(){
synchronized(Singleton2.class){ // 使用 synchronized 块,临界资源的同步互斥访问
if (singleton2 == null) {
singleton2 = new Singleton2();
}
}
return singleton2;
}
}
同步延迟加载 — 使用静态内部类实现延迟加载
效率较高,延迟加载的,用时才初始化。
public class Singleton5 {
// 私有内部类,按需加载,用时加载,也就是延迟加载
private static class Holder {
private static Singleton5 singleton5 = new Singleton5();
}
private Singleton5() {
}
public static Singleton5 getSingleton5() {
return Holder.singleton5;
}
}
双重检测同步延迟加载
效率较高,因为这里的同步只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要同步获取锁了。volatile 关键字可以防止new对象时的指令重排序。
// 线程安全的懒汉式单例
public class Singleton3 {
//使用volatile关键字防止重排序,因为new Instance()是一个非原子操作,可能创建一个不完整的实例
private static volatile Singleton3 singleton3;
private Singleton3() {
}
public static Singleton3 getSingleton3() {
// Double-Check idiom
if (singleton3 == null) {
synchronized (Singleton3.class) { // 1
// 只需在第一次创建实例时才同步
if (singleton3 == null) { // 2
singleton3 = new Singleton3(); // 3
}
}
}
return singleton3;
}
}
借助于 ThreadLocal,我们可以实现双重检查模式的变体。我们将临界资源instance线程私有化(局部化),具体到本例就是将双重检测的第一层检测条件 if (instance == null) 转换为线程局部范围内的操作
public class Singleton {
// ThreadLocal 线程局部变量,将单例instance线程私有化
private static ThreadLocal<Singleton> threadlocal = new ThreadLocal<Singleton>();
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
// 第一次检查:若线程第一次访问,则进入if语句块;否则,若线程已经访问过,则直接返回ThreadLocal中的值
if (threadlocal.get() == null) {
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查:该单例是否被创建
instance = new Singleton();
}
}
threadlocal.set(instance); // 将单例放入ThreadLocal中
}
return threadlocal.get();
}
}
创建索引的表应当是:数据大、查询频繁,但更新较慢的表,小表不建议使用索引。
主键唯一标识一条记录,不能有重复,不能为空。外键主要是用来控制数据库中的完整性,当对一个表进行操作时,和他有关联的一个表或多数据能够同发生改变 。
Mysql5.5+的版本默认引擎都是InnoDB,早期的Mysql版本默认的引擎是MyISAM。
MyIsam 和 InnoDB的适用场景
MyISAM适合:(1)做很多count 的计算;(2)插入不频繁,查询非常频繁;(3)没有事务。
InnoDB适合:(1)可靠性要求比较高,或者要求事务;(2)表更新和查询都相当的频繁,并且表锁定的机会比较大的情况。
MyIsam 和 InnoDB的区别
1)MyISAM类型不支持事务处理等高级处理,而InnoDB类型支持
2)MyIsam表不支持外键
3)在执行数据库写入的操作(insert,update,delete)的时候,MyIsam表会锁表,而innodb表会锁行
4)当你的数据库有大量的写入、更新操作而查询比较少或者数据完整性要求比较高的时候就选择innodb表。当你的数据库主要以查询为主,相比较而言更新和写入比较少,并且业务方面数据完整性要求不那么严格,就选择mysiam表。因为mysiam表的查询操作效率和速度都比innodb要快
1.选取最适用的字段属性
2.使用连接(JOIN)来代替子查询(Sub-Queries)
3、使用联合(UNION)来代替手动创建的临时表
4、使用事务,保持数据库中数据的一致性和完整性。
5、锁定表??
6、使用外键,保证数据的关联性
7、使用索引,索引应建立在用于JOIN
,WHERE
判断和ORDER BY
排序的字段上。尽量不要对数据库中某个含有大量重复的值的字段建立索引。
8、优化的查询语句:最好是在相同类型的字段间进行比较的操作、在建有索引的字段上尽量不要使用函数进行操作、搜索时尽量不要使用LIKE关键字和通配符
A:atomiciy原子性
一个事务必须保证其中的操作要么全部执行,要么全部回滚,不可能存在只执行了一部分这种情况出现。
C:consistency一致性
数据必须保证从一种一致性的状态转换为另一种一致性状态。
比如上一个事务中执行了第二步时系统崩溃了,数据也不会出现bill的账户少了100块,但是tim的账户没变的情况。要么维持原装(全部回滚),要么bill少了100块同时tim多了100块,只有这两种一致性状态的
I:isolation隔离性
在一个事务未执行完毕时,通常会保证其他Session 无法看到这个事务的执行结果
D:durability持久性
事务一旦commit,则数据就会保存下来,即使提交完之后系统崩溃,数据也不会丢失。
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted) | 是 | 是 | 是 |
不可重复读(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
>
什么是树(平衡树,排序树,B树,B+树,R树,红黑树)、堆(大根堆、小根堆)、图(有向图、无向图、拓扑)
栈和队列的相同和不同之处
栈通常采用的两种存储结构
两个栈实现队列,和两个队列实现栈
J2EE框架,提供了轻量级IOC的良好支持,同时提供了AOP技术非常好的封装。
关键词:超级大工厂,对象控制权,解耦对象间的依赖关系
控制反转,对象控制权由调用者移交给容器,使得调用者不必关心对象的创建和管理,专注于业务逻辑开发;
优秀的解耦方式,解耦对象间的依赖关系,避免通过硬编码的方式耦合在一起;
底层实现:反射机制;
关键词:模块化、交叉关注点、横切性质的系统级业务
面向切片编程,一种新的模块化方式,专门处理系统各模块中的交叉关注点问题,将具有横切性质的系统级业务提取到切面中,与核心业务逻辑分离(解耦);
便于系统的扩展,符合开-闭原则;
动态AOP的实现,Java动态代理(接口代理)与cglib(类代理),具体由Bean后处理器生成代理;
AOP理念实践:Spring AOP,Java Web Filter,Struts2 Interceptor, SpringMVC Interceptor
返回的是一个代理对象,如果目标对象实现了接口,则spring使用jdk 动态代理技术,如果目标对象没有实现接口,则spring使用CGLIB技术.
struts框架是有自定义标签、资源信息、Servlet和JSP组成的MVC模式的框架。