数据库及垃圾回收相关拾遗

Redis原子性:
1.单个操作或者单条命令具有原子性 2.redis支持简单的事务,可以监听一个watch,开启事务,如果期间监听的值没有改变,则继续,否,则回滚
3.数据流具有原子性,即pipleine 4.lua命令具有原子性

数据库的悲观锁和乐观锁
1.悲观锁也可以称作独占锁,如果是主键查询可以对单行加锁,否则是对整个表加锁,这样回导致程序不断的挂起,释放,高并发下很耗费系统资源
2.乐观锁:乐观锁即先保存数据的旧值,修改的时候先和旧值比较,如果相同则更新,否则回滚,这样存在两个问题,
A)ABA问题,即县城1再t1时刻保存了A的旧值,在线程1修改A之前,线程2先把A的值改为了B ,然后又改为了A,这样虽然比较相同,但是已经不是原来的值
--对值加一个版本判断,修改一次,版本+1,比对值的时候同时比对版本
B)有可能会出现重复失败的问题,
--增加失败判断,失败重试,重试三次才算真正的失败
乐观锁的实现思想来源于底层的CAS原理

数据库事务的隔离级别
首先介绍多个事务并发会产生数据库更新的问题,以此将数据库事务的隔离级别分为多个层次:脏读,读/写提交,可重复读,序列化
先说一下两类丢失更新:
一。事务1的过程中,事务2修改了A的值,事务1回滚时丢失了事务2对A的更新
二。事务1和事务2同时进行,事务1和事务2都更新了A的值,比如事务1将1000修改成了900,事务2也将A的值从1000修改成了900,但是此时
事务1和事务2都对A进行了减操作,最后的值应该是800
为了避免这两种丢失更新的情况,数据库标准规范定义了事务之间的隔离级别
一。脏读,含义是允许一个事务读取另一个事务中未提交的数据,那么问题出现了,比如第二类丢失更新情况,因为事务2修改的值对事务1
可见,所以事务1最后提交事务的时候最终值应该是800,但是如果事务2出现了问题进行回滚,那么事务1的值将依然是800,但是实际上因该是
900,
二。读/写提交,那么第二个隔离级别出现了,他的含义是:一个事务只能读取另一个事务已提交的数据,那么依然会出现问题:事务1在t1时刻
读到了A的值1000,他准备付款,事务2同时执行,在事务1付款前他消费了500并提交了事务,因为事务间已提交数据可见,这时当事务1付款时
发现余额不足,但是之前读到的值明明为1000,这样的场景成为不可重复读
三。可重复读:这时就有了第三个隔离级别,可重复读是针对数据库的同一条记录而言,可重复读会使得同一条数据库记录的读/写按照一个序列化
进行操作,不会产生交叉情况,这样能保证一跳数据的一致性,进而保证上述场景的正确性,比如事务1对table1进行查询打印,查询出来有10条
记录,但是同时事务2在table1中插入了一条数据,这样就会导致事务1最后打印出来的数据有11条,一位内可重复读是针对同一条记录,这里他们不是同一条记录
四。序列化:事务分开进行,互不相扰
数据库的存储引擎:
数据库都是用各种不同的技术将数据存储在文件中或者内存中,这些技术中,每一种技术都是用不同的存储机制,索引技巧,锁定水平并提供不同的
功能和能力,这些不同的技术以及配套的相关功能就是存储引擎。
以mysql为例:mysql支持多个存储引擎,ISAM,myisam,memory,innodb/bdb ,ndb cluster

ISAM >这是一个定义明确且历时实践考究的数据表格管理法,他在设计之时就考虑到数据库被查询的次数要远大于更新的次数,一次,ISAM执行读取操作的速度很快
,而且不占用大量的内存和存储资源,ISAM两个不足之处主要是 不支持事务管理,不能容错,如果你的硬盘崩溃了,那么数据文件就无法恢复了

MYISAM >管理非事务表:这是ISAM的扩张模式,除了提供ISAM里所没有的索引和字段管理的大量功能,MYISAM还使用一种表格锁定的机制,来优化
多个并发的读写操作,它提供告诉存储和检索,以及全文搜索能力,受到web开发的青睐,MYISAM在所有的mysal配置里被支持,市默认的
存储引擎
MEMORY > MEMORY存储引擎提供"内存中"表,被正式确定为HEAP引擎,也处理非事务表,heap允许只驻留在内存里的临时表格驻留在内存里让heap
比ISAM和MYISAM都快,但是它所管理的数据是不稳定的,而且如果在关机之前没有进行保存,那么所有的数据都会丢失,在数据行被删除的时候,
heap也不会浪费大量的空间,heap表格在你需要使用select表达式来选择和操控数据的时候非常有用,但是,要记住,在用完表格之后就删除表格
INNODB和BDB >这两种存储引擎提供事务安全表,尽管要比ISAM和MYISAM要慢得多,但是INNODB和BDB包括了对事务处理和外键的支持,这两点是前两个数据库
引擎所灭有的
NDB Cluster >这是用来实现分割到多台计算机上的表的存储引擎,当前只被linux,solaris 和mac os 支持
更换存储引擎:全局,更改服务器配置,在mysql.ini或my.ini中更改default-storage-engine即可
按表:建表时通过engine或者type来指定存储引擎, create table t(i int) engine=innodb 或create table t(i int) type=memory
如何选择:
如何选择存储引擎需要考虑每个存储引擎提供了那些不同的核心功能,一般考虑四个 -->支持的字段和数据类型
1。支持的字段和数据类型,虽然所有的这些引擎都支持通用的数据类型,如整形,字符型,但是并不是所有的引擎都支持其他的字段类型
,比如BLOG(二进制大对象),text文本类型
2.锁定类型:锁定机制主要是防止多个线程同时更新同一个数据,不同的存储引擎支持不同的级别的锁定,表锁定,页锁定,行锁定
表锁定开销小,加锁快,不会出现死锁,锁定粒度大,发生锁冲突的概率最高,并发度低,支持最多的就是表锁定,
MYISAM,MEMORY,支持这种锁定,mysql的表级锁定有两种模式,表gong享读和表du占写锁, 对myisam表的读操作不会阻塞其他用户对同一张表的读
请求,但是会阻塞其他用户对同一张表的读和写操作,myisam表的读操作和写操作之间,以及写操作之间是串行的。
行锁定,开销大,加锁慢,会出现死锁,锁粒度小,发生锁冲突的概率最低,并发量也最高,innodb进行行级锁定
页锁定,开销和加锁实践介于表锁定和行锁定之间,会出现死锁,锁粒度和并发度也是在前两种之间,berkeleydb引擎支持页锁定
3。索引:建立索引在搜索和回复数据库中的数据的时候能够显著的提高性能,不同的存储引擎提供不同的制作索引的技术,有些存储引擎不支持素银
4.事务处理,innodb和bdb支持事务,myisam不支持
网络模型:
select库:首先,创建所关注的实践的文件描述符集合,对于一个描述符,可以关注其上面的读事件,写事件,以及异常发生事件,所以要创建三类事件描述符集合
,分别用来收集读事件 ,写事件,异常发生事件,其次,调用底层提供的select函数,等待事件的发生,这里需要主义的一点是,select的阻塞与是否设置非阻塞io是
没有关系的,然后,轮询所有事件描述符集合中的每一个事件描述符,检查是否有相应的事件发生,如果有就进行处理。
poll库:select和poll的区别是。select库需要为读事件,写事件,异常发生事件分别创建一个描述符集合,一次在最后轮询的时候需要分别轮询这三个集合,二poll
库则只需要创建一个集合,在每个描述符对应的结构上分别设置读事件,写事件,异常发生事件
epoll库:从前面我们知道,他们的处理方式都是创建一个待处理事件列表,然后吧这个列表发给内核,返回的时候再去轮询检查这个集合来判断事件是否发生
,这样在描述符比较多的应用中,效率就显得比较地下了,一种比较好的做法是,吧描述符列表的管理交给内核,一旦有某种事件发生,内核吧发生事件的描述符
通知给进程,这样就避免了轮询这个描述符列表,epoll就是这样
java的垃圾回收:
一。判断对象是否存活:
A:引用计数法:给对象添加一个引用计数器,每当有一个地方引用它时。引用计数器+1,当引用失效时,计数器-1,任何时刻,计数器为0的
对象就是不可能再被使用的对象。
优点:实现简单,判断效率也很高
缺点:很难解决对象之间的相互引用问题比如Object A = new Object();Object B = new OBject();A.obj = b , B.obj = A ,这两个对象实际上没有用到了
但是他们的引用计数器都是1,不会被回收
B:可达性分析:这种算法目前定义了几个root,也就是这几个对象是jvm虚拟机不会被回收的对象,所以这些对象引用的对象都是在使用中的对象,这些对象未使用的对象就是即将要被回收的对象。
简单就是说:如果对象能够达到root,就不会被回收,如果对象不能够达到root,就会被回收。
以下对象会被认为是root对象:
被启动类(bootstrap加载器)加载的类和创建的对象
jvm运行时方法区类静态变量(static)引用的对象
jvm运行时方法去常量池引用的对象
jvm当前运行线程中的虚拟机栈变量表引用的对象
本地方法栈中(jni)引用的对象
在判断对象是否存活时,需要在一个能确保一致性的快照中进行,这里的一致性的意思是在整个分析期间,这个执行系统看起来就像是被冻结在某个时间点上,
不可以出现分析过程中对象引用关系还在不断变化的情况,该点不满足的话分析结果的准确性就不能得到抱枕,这点导致gc进行时需要停顿所有执行线程
也就是sun所说的stop the world
二。再判断对象是否存活过后,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历再次标记过程
1).第一次标记并进行一次筛选。
筛选的条件是此对象是否有必要执行finalize()方法。
当对象没有覆盖finalize方法,或者finzlize方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”,对象被回收。

2).第二次标记
如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为:F-Queue的队列之中,并在稍后由一条虚拟机自动建立的、
低优先级的Finalizer线程去执行。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。这样做的原因是,如果一个对象finalize()
方法中执行缓慢,或者发生死循环(更极端的情况),将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。
Finalize()方法是对象脱逃死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象要在finalize()
中成功拯救自己----只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。
如果对象这时候还没逃脱,那基本上它就真的被回收了。
需要注意的是:finalize()方法只会被虚拟机调用一次,如果对象面临下一次回收,将不会再执行finalize方法;实际开发中,我们应该尽量避免
使用finalize方法使对象逃脱回收
三。垃圾回收的算法
1。标记清除算法:它分为两个阶段,标记, 清除,-首先标记需要回收的对象,标记后统一回收标记的对象
优点:思想简单,也容易实现
缺点:首先是效率问题,标记,清除两个过程效率都不高,其次是标记清除后会产生大量的内存碎片,当需要申明一个大对象时
可能会导致无法找到连续的内存而导致提前进行垃圾回收
2.复制算法:为了解决效率问题而产生了复制算法,它的基本思想是,它将内存按容量分为大小相同的两块,每次只使用其中一块,如果这一块用完了,
就将存活的对象复制到另一块,然后把这一块内存中的所有对象清除。
有点:效率高,回收后不会产生内存碎片
缺点:可用内存变成了原来的一半
说明:现在的商用虚拟机基本上都是采用的这种算法,因为实际开发中,基本上98%的对象都是朝生慕死,所以并不是按照1:1的比例来划分内存空间,
而是分为一块较大的Eden空间和两块较小的survivor空间,每次使用eden和其中一块survivor空间,回收时,将eden和survivor中存活的对象复制到另一块survivor中,
最后清理掉eden和survivor, hotspot虚拟机默认的分配比例是eden:survivor=8:1也就是新生代中可用内存空间为整个新生代内存空间的90%(80+10)
3.标记-整理算法:复制算法再存活对象较多时,就需要进行更多的复制操作,效率就会变得很低,更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,
以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。根据老年代的特点,有人提出了标记-整理算法,标记过程和标记清理算法的标记
过程以银行,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后清理掉边界意外的内存;
当代的虚拟机的垃圾回收都是采用分代收集算法,就是根据对象的存活周期将内存划分为几块,一般食分为新生代和老年代,根据每个年代
的特点采用最适合的收集算法,新生代对象存活率低,所以复制算法最合适,老年代对象存活率高,就必须用标记清除或标记整理算法。
四:hotspot虚拟机算法实现
1.枚举根节点,由于目前主流的虚拟机使用的都是准确式gc,所以当执行系统停顿下来后,并不需要一个不漏的检查对象全局的引用位置,虚拟机应但是有办法直接得知那些地方存放着对象引用,再
在HotSpot的实现中,是使用一组称为OopMap的数据结构来达到这个目的的,在类加载完成的时候,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,
在JIT编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用。 这样,GC在扫描时就可以直接得知这些信息了
2.安全点:额外存放对象引用OopMap是需要分配内存的,随着对象的引用关系不断复杂,内存也变得越来越大,这样的OopMap对于GC来说,提高的效率就不明显了。所以,并不是每条指令都生成了OopMap,
只是在“特定的位置”记录了这些信息,这些位置称为安全点(Safepoint),即程序执行时并非在所有地方都能停顿下来开始GC,只有在到达安全点时才能暂停。
Safepoint的选定既不能太少以致于让GC等待时间太长,也不能过于频繁以致于过分增大运行时的负荷。
另一个需要考虑的问题是如何在GC发生时让所有线程(这里不包括执行JNI调用的线程)都“跑”到最近的安全点上再停顿下来。 这里有两种方案可供选择:抢先式中断(Preemptive Suspension)
和主动式中断(Voluntary Suspension),其中抢先式中断不需要线程的执行代码主动去配合,在GC发生时,首先把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程,让它“跑”到安全点上。
现在几乎没有虚拟机实现采用抢先式中断来暂停线程从而响应GC事件。
3.和安全区域:安全区域是扩展了的安全点,也就是整块区域都是安全的,可以执行GC,为什么要这么做,主要是考虑到执行的程序在不太长的时间就会遇到安全点,进入GC,但是程序不执行是呢,比如遇到sleep,block状态,它就无法响应JVN中断。
安全区域是指在一段代码片段之中,引用关系不会发生变化。 在这个区域中的任意地方开始GC都是安全的。
在线程执行到Safe Region中的代码时,首先标识自己已经进入了Safe Region,那样,当在这段时间里JVM要发起GC时,就不用管标识自己为Safe Region状态的线程了。 在线程要离开Safe Region时,它要检查系统是否已经完成了根节点枚举
(或者是整个GC过程),如果完成了,那线程就继续执行,否则它就必须等待直到收到可以安全离开Safe Region的信号为止
五:Java类不会被初始化的场景:
1.子类调用父类的静态字段,只会初始化父类,不会初始化子类
2.new一个类的数组对象,该类不会被初始化,(java数组类本身不通过class加载器加载,而是由虚拟机直接创建)
3.调用类的静态final常量,该类不会被初始化
六:netty:高性能NIO框架
一。netty组件
1.Channel:可以理解为IO数据的载体,提供了IO操作的API
2.回调:再Netty中使用回调来处理事件
3.Future:提供再每一个操作完成时通知应用程序的方式,他可以配合ChannelFutureLisener使用,事件完成后会回调ChannelFutureLisener中的方法
4.ChannelHandler:提供入站和出战的事件处理器
注意:绑定ChannelHandler后,handler的执行顺序,如果是入站,则是先添加先执行,如果是出站,则是先添加后执行
每一个Channel的所有IO操作都会有一个evenLoop也是一个线程来执行,同一个Channel的所有io事件都是由同一个evenloop来执行,所以不用考虑线程安全的问题
handler中的事件回调方法,如果再一个handler中没有覆盖该方法,那么该事件会传递到下一个handler,如exceptionCaught方法,如果再一个
handler中没有捕获异常,那么他会转发给下一个Chanelhandler处理,如果没有一个handler实现该方法,那么异常会传递到channelpipline并记录
channel用于io操作,channelhandler用来实现业务事件,channelpipeline可以看作是channelhandler的流水线容器,channelhandlercontext则是上下文
,它可以用来获取底层channel或者是用来传递事件
二。关于bytebuf
bytebuf是netty封装的一个雷士jdk bytebuffer的一个数据缓冲区类,不同的是bytebuffer只有一个指针,但是bytebuf有读指针和写指针
不同的操作可以移动相应的指针
1。堆缓冲区:将数据存储再jvm的堆空间内,它能再没有使用池化的情况下提供快速的分配和释放
2.直接缓冲区:jdk1.4中引入的bytebuffer类允许jvm实现通过本地调用来分配内存,这主要是为了避免再每次调用本地io操作之前或者之后将
缓冲区的内容复制到一个中间缓冲区,(或者从中间缓冲区复制到缓冲区)但是直接缓冲区相较于堆缓冲区,他的释放和分配都比较昂贵
3.符合缓冲区:netty零拷贝的一个体现,合并多个bytebuf
三:netty的线程模型
1.线程池技术:线程池能够初始化一些线程提供使用,可以重用线程减少了线程的创建和销毁的开销,也为线程管理提供了方便
但是它并不能消除线程上下文切换带来的开销,
线程上下文切换:CPU的执行并不是连续的,它有可能前一毫秒被线程1使用,下以毫秒为线程2服务,那么当从线程1切换到线程2时,需要备份线程
1的运行数据,然后切换到线程2,这个就是线程的上下文切换,线程越多,上下文切换也越多,效率越低,所以线程并不是越多越好。
2。ChannelFutuer中的两个常用方法sync()/syncUninteruptibly()和wait()/waitUninteruptibly() 这两个方法以Uninteruptibly结尾的方法会在阻塞期间忽略interupt
并在结束后interupt当前线程,而不是以Uninteruptibly结尾的再wait中当前程被interupt时,会抛出异常,而sync和await的区别是:都是会阻塞当前贤臣给,不同的是
如果发生异常,sync会抛出异常,await不会
补充:线程Thread的interupt方法会把当前的interupt标志置为true,并不会做其他的什么,其他方法可以检测该字段并抛出interupt异常,比如THread的sleep方法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值