美文网首页
jdk读书笔记

jdk读书笔记

作者: 幸南 | 来源:发表于2020-04-23 23:52 被阅读0次

    双亲委派机制:
    沙箱安全机制:
    自己写的String.class类不会被加载,防止核心API库被随意篡改
    避免被重复加载:

    jar包加载时是按需加载,运行时动态加载
    

    CMS:
    初始标记
    并发标记
    重新标记
    并发清除

    优点:
        并发收集
        低停顿
    缺点:
        对CPU资源敏感
        无法处理浮动垃圾
        使用的标记清除算法会产生大量空间碎片
    

    1.JVM的内存分配和回收
    1.1.对象优先在Eden区分配
    当Eden区没有足够空间时将发起一次Minor GC
    Minor GC和Full GC的不同:
    Minor GC:发生在新生代的垃圾收集动作,非常频繁
    Major GC/Full GC:发生在老年代的GC,一般出现一次Major GC会伴随一次Full GC
    1.2.大对象直接进入老年区
    1.3.长期存活的对象将进入老年代
    对象在Eden区出生并经过一次Minor GC之后任然能够存活,并且被Survivor区接纳之后,将被移动到
    Survivor空间中,并将对象年龄设置为1,之后每经过一次Minor GC,年龄就增加一岁,当达到15岁(默认)
    时,会被晋升到老年代中.
    2.如何判断对象可以被回收
    2.1.引用计数法
    缺点是无法确定循环引用
    2.2.可达性算法
    以GC roots对象作为起点,从起点往下进行搜索,形成一个引用链
    GC roots:类加载器,Thread,虚拟机栈的本地变量表,static成员,常量引用,本地方法栈的变量等等.
    2.3.finalize()方法最终判定对象是否存活
    第一次标记进行一次筛选,第二次标记之后才回收
    第一次:
    条件是此对象是否有必要执行finalize()方法,如果未覆盖或者方法已经执行,虚拟机都视为没有必要执行,对象回收
    第二次:
    如果判定有必要执行,会把对象放入一个F-Queue的队列中,等到一定的时间虚拟机将会自己创建一个低优先级的线程(Finalizer)
    由该线程去触发finalize()方法,若finalize()方法中将此对象与还在与GC
    Roots相连的对象相关联,那么该对象就不会被回收,否则该对象就会被回收
    finalize方法是对象逃脱死亡的最后一次机会,只要与引用链上的任何一个对象建立关联即可.
    2.4.如何判断一个常量是废弃常量
    如果没有任何对象引用该常量的话,就说明是废弃常量.
    2.5.如果判断一个类是无用的类
    有三个条件:
    a.该类所有的实例已经被回收
    b.加载该类的ClassLoader已经被回收
    c.该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法.

    3.垃圾回收算法
    垃圾收集算法:
    a.标记-清除算法
    容易产生内存碎片
    b.复制算法
    c.标记-整理算法
    d.分代收集算法
    垃圾收集器:
    a.Serial收集器
    单线程,会出现Stop The World
    新生代采用复制算法,老年代采用标记-整理算法
    依然是HotSpot在Client模式下默认的新生代收集器;

            也有优于其他收集器的地方:
            简单高效(与其他收集器的单线程相比);
            对于限定单个CPU的环境来说,Serial收集器没有线程交互(切换)开销,可以获得最高的单线程收集效率.
            在用户的桌面应用场景中,可用内存一般不大(几十M至一两百M),可以在较短时间内完成垃圾收集(几十MS至一百多MS)
            只要不频繁发生,这是可以接受的
            -XX:+UseSerialGC:显式指定
        b.ParNew收集器
            ParNew垃圾收集器是Serial收集器的多线程版本
            在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作.
            -XX:+UseConcMarkSweepGC:指定使用CMS后,会默认使用ParNew作为新生代收集器.
            -XX:+UseParNewGC:强制指定使用ParNew收集器.
            -XX:ParallelGCThreads:指定垃圾收集的线程数.
            非并发
            并行收集:多条垃圾收集器同时工作,用户线程处于等待状态.
            并发收集:用户线程与垃圾收集器线程同时工作,用户线程在继续运行,垃圾收集程序运行于另外一个cpu上.
        c.Parallel Scavenge收集器
            Parallel Scavenge垃圾收集器因为与吞吐量关系密切,也称为吞吐量收集器.
            有一些特点与ParNew收集器相似:
                新生代收集器
                采用复制算法
                多线程收集
            主要特点:关注点与其他收集器不同
                CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间
                而Parallel Scavenge收集器的目标则是达一个可控制的吞吐量
                高吞吐量为目标,即减少垃圾收集时间(就是每次垃圾收集时间短,但是收集次数多),让用户代码获得更长的运行时间
            参数:
                -XX:MaxGCPauseMillis:最大垃圾收集停顿时间
                -XX:GCTimeRatio:设置垃圾收集时间占总时间的比率,0<n<100的整数
                GCTimeRatio相当于设置吞吐量大小
                    垃圾收集执行时间占应用程序执行时间的比例的计算方法是:
                        1/(1+n)
                    默认值是1%--1/(1+99),即n=99
                    垃圾收集所花费的时间是年轻一代和老年代收集的总时间
                -XX:+UseAdptiveSizePolicy
                    使用GC自适应的调节策略
            吞吐量:
                CPU用于运行用户代码的时间与CPU总消耗时间的比值
                即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
            目标:
                停顿时间
                吞吐量
                覆盖区域
        d.CMS收集器
            获取最短回收停顿时间为目标的收集器
            基于标记-清除算法的收集器,共四步:
                a.初始标记
                    初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快
                    该过程分为两步
                        a1.标记GC Roots可达的老年代对象
                        a2.遍历新生代对象,标记老年代对象
                    暂停所有的其他线程(STW)
                b.并发标记
                    同时开启GC和用户线程,用一个闭包结构去记录可达对象.这个阶段并不能保证包含当前所有的可达对象.
                c.重新标记
                    修正并发标记期间因为用户程序继续运行进而导致标记产生变动的那一部分对象的标记记录.
                d.并发清除
                    开启用户线程,同时GC线程开始对未标记的区域做清除
            初始标记、重新标记着两个步骤任然需要"Stop The World"
            缺点:
                1.对CPU资源敏感.
                2.无法处理浮动垃圾(在java业务程序线程与垃圾收集器并发执行过程中又产生的垃圾)
                3.使用的标记-清除算法会导致收集结束时会有大量空间碎片产生
    
        e.G1收集器
            面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器,以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征.
            设计原则是首先收集尽可能多的垃圾.
            分区概念:
                1.分区(Regin)
                    将整个堆空间分成若干个大小相等的内存区域,每次分配对象空间将逐段地使用内存.并不要求对象的存储是连续的.
                    将整个堆分为了2048个分区.
                2.卡片(Card)
                    将每个分区内部分为若干个512Byte的卡片
                3.堆(Heap)
                    G1同样可以通过-Xms/-Xmx来指定堆空间大小
    
            G1收集器是在JDK1.7开始可以设置使用,在JDK1.9时设置为默认垃圾收集器。G1收集器和其他收集器相比有以下特点
            并行与并发:G1能充分利用多CPU、多核的硬件优势,来缩短Stop-The-World停顿时间
            分代收集:和其他收集器相同,分代概念依然保留。G1收集器不需要其他收集器的配合就可以管理整个堆,
            可以根据不同的方式去处理新创建的对象、存活了一段时间的对象和熬过多次GC的对象。
            空间整合:不同于CMS的标记-清理,G1采用的是标记-整理的方式来处理碎片化的空间。
            这种方式意味着G1收集器运作期间不会产生内存空间碎片,收集后提供规整的可用空间
            可预测的停顿:G1相对于CMS另外一个更大的优势,就是可以设置可预测的停顿模型,能够使开发者明确指定
            在长度为M毫秒的时间片段内,消耗在垃圾收集器上的时间不能超过M毫秒。
    

    -XX:+UseCompressedOops选项,JVM会使用32位的OOP,而不是64位的OOP.

    总线锁。缓存锁和缓存一致性协议
    嗅探协议
    MESI协议:
    是以缓存行(缓存的基本数据单位,在Intel的CPU上一般是64字节)的几个状态来命名的(全名是Modified、Exclusive、 Share or Invalid)
    该协议要求在每个缓存行上维护两个状态位,使得每个数据单位可能处于M、E、S和I这四种状态之一,各种状态含义如下:

       M:被修改的。处于这一状态的数据,只在本CPU中有缓存数据,而其他CPU中没有。同时其状态相对于内存中的值来说,是已经被修改的,且没有更新到内存中
       E:独占的。处于这一状态的数据,只有在本CPU中有缓存,且其数据没有修改,即与内存中一致。
       S:共享的。处于这一状态的数据在多个CPU中都有缓存,且与内存一致。
       I:无效的。本CPU中的这份缓存已经无效。
    
         一个处于M状态的缓存行,必须时刻监听所有试图读取该缓存行对应的主存地址的操作,如果监听到,则必须在此操作执行前把其缓存行中的数据写回CPU。
        一个处于S状态的缓存行,必须时刻监听使该缓存行无效或者独享该缓存行的请求,如果监听到,则必须把其缓存行状态设置为I。
        一个处于E状态的缓存行,必须时刻监听其他试图读取该缓存行对应的主存地址的操作,如果监听到,则必须把其缓存行状态设置为S。
    
       当CPU需要读取数据时,如果其缓存行的状态是I的,则需要从内存中读取,并把自己状态变成S,如果不是I,则可以直接读取缓存中的值,但在此之前,必须要等待其他CPU的监听结果,如其他CPU也有该数据的缓存且状态是M,则需要等待其把缓存更新到内存之后,再读取。
    
       当CPU需要写数据时,只有在其缓存行是M或者E的时候才能执行,否则需要发出特殊的RFO指令(Read Or Ownership,这是一种总线事务),通知其他CPU置缓存无效(I),这种情况下性能开销是相对较大的。在写入完成后,修改其缓存状态为M。
    
    
    可见性问题
    乱序执行
    

    JMM内存模型

    JMM如何解决原子性,可见性,有序性的问题

    volatile和synchronized

    volatile(解决可见性(lock),防止指令重排序)
    Thread.join是基于wait和notify实现的

    每个对象主要由一个对象头、实例变量、填充数据三部分组成
    JVM采用两个字节来存储对象头(如果是数组就是3个字节,最后一个字节为数组长度),其结构主要为Mark World和Class MetaData Address组成
    其中Mark Word在默认情况下存储着对象的HashCode、分代年龄、锁标记位等.

    CPU缓存,处理器优化,指令重排序
    限制处理器的优化以及使用内存屏障

    JMM抽象模型的统一规范
    处理器的优化,指令重排序
    1.编译器的优化重排序
    2.cpu的指令重排序
    3.内存系统的重排序

    编译器的乱序:
    不改变单线程语义的前提下
    int a=1; (1)(2)
    int b=1; (2)(1)

    store barrier/load barrier/full barrier
    1.保证数据的可见性
    2.防止指令之间的重排序

    store barrier:
        store barrier称为写屏障,相当于storestore barrier, 强制所有在storestore内存屏障之前的所有执行,都要在该内存屏障之前执行,并发送缓存失效的信号。所有在storestore barrier指令之后的store指令,都必须在storestore barrier屏障之前的指令执行完后再被执行。也就是进制了写屏障前后的指令进行重排序,是的所有store barrier之前发生的内存更新都是可见的(这里的可见指的是修改值可见以及操作结果可见)
    
    load barrier:
        load barrier称为读屏障,相当于loadload barrier,强制所有在load barrier读屏障之后的load指令,都在load barrier屏障之后执行。也就是进制对load barrier读屏障前后的load指令进行重排序, 配合store barrier,使得所有store barrier之前发生的内存更新,对load barrier之后的load操作是可见的.
    
    full barrier:
        full barrier成为全屏障,相当于storeload,是一个全能型的屏障,因为它同时具备前面两种屏障的效果。强制了所有在storeload barrier之前的store/load指令,都在该屏障之前被执行,所有在该屏障之后的的store/load指令,都在该屏障之后被执行。禁止对storeload屏障前后的指令进行重排序.
    
    总结:
        内存屏障只是解决顺序一致性问题,不解决缓存一致性问题,缓存一致性是由cpu的缓存锁以及MESI协议来完成的.
        而缓存一致性协议只关心缓存一致性,不关心顺序一致性。所以这是两个问题.
    

    在java中的体现:

    并发事务带来的问题:
    1.脏读
    2.可重复读
    3.读已提交
    4.幻读
    不可重复读和幻读的不同:
    不可重复读是修改,幻读是新增或者删除
    MySQL四个隔离级别:
    读未提交
    读已提交
    可重复读
    串行化

    锁分类:
    公平锁/非公平锁
    可重入锁
    独享锁/共享锁
    互斥锁/读写锁
    乐观锁/悲观锁
    分段锁
    偏向锁/轻量级锁/重量级锁
    自旋锁

    IOC容器加载过程:
    1.刷新预处理
    2.将配置信息解析,注册到BeanFactory
    3.设置bean的类加载器
    4.如果有第三方想再bean加载注册完成后,初始化前做点什么(例如修改属性的值,修改bean的scope为单例或者多例。),提供了相应的模板方法,后面还调用了这个方法的实现,并且把这些个实现类注册到对应的容器中
    5.初始化当前的事件广播器
    6.初始化所有的bean
    7.广播applicationcontext初始化完成

    JWTtoken:
    头部
    type
    algm
    载荷
    用户身份信息
    注册声明
    签名
    签名

    锁的获取过程:
    自旋锁
    偏向锁
    锁不仅不存在竞争,并且都是由同一个线程获得

    wait或者notify为什么要先获取锁
    
    wait和sleep的区别
    

    Docker
    FROM:基础镜像,当前新镜像是基于哪个镜像的
    MAINTAINER:镜像维护者的姓名和邮箱地址
    RUN:镜像构建时需要运行的命令
    WORKDIR:容器创建后,默认在哪个目录
    EXPOSE:当前容器对外暴露的端口
    ENV:用来在构建镜像时设置的环境变量
    COPY:将宿主机的目录下的文件copy进镜像且ADD命令会自动解压压缩包
    VOLUME:容器数据卷,用于保存和持久化
    CMD:指定容器启动过程中需要运行的命令
    CMD命令如果写了多条,只有最后一条生效
    CMD命令会被docker run之后的参数替换
    ENTRYPOINT:指定容器运行过程中需要的命令
    ENTRYPOINT会把docker run命令的参数追加到后面
    ONBUILD:

    死锁条件:
    1.互斥条件
    2.请求和保持条件
    3.循环等待条件
    4.不剥夺条件

    hash函数处理冲突方法:
    开放定址法
    链地址
    再hash
    建立公共溢出区
    基本思想:建立基本表和溢出表,如果基本表已经存在这个元素,放入溢出表
    hashmap是使用的链地址法

    HashMap高并发问题:
        1.死锁
        2.fail-fast问题
    在HashMap中不能由get()方法来判断HashMap中是否存在某个key,应该用containsKey()方法来判断
    
    在jdk1.8中,如果链表长度大于8且节点数组长度大于64的时候,就把链表下所有的节点转为红黑树。
    

    线程池队列:
    1.有界队列
    2.无界队列
    3.同步移交队列

    synchronized 语句需要一个对象的引用;随后会尝试在该对象的管程上执行 lock 动
    作,如果 lock 动作未能成功完成,将一直等待。当 lock 动作执行成功,就会运行
    synchronized 语句块中的代码。一旦语句块中的代码执行结束,不管是正常还是异
    常结束,都会在之前执行 lock 动作的那个管程上自动执行一个 unlock 动作。

    MySQL选择数据类型原则
    1.越小的一般越好
    2.简单
    3.尽量避免null

    MySQL的VARCHAR类型是不定长存储,但是有例外,如果表参数有ROW_FORMAT=FIXED的话会定长存储
    VARCHAR类型使用额外的两个字节存储长度,如果小于255是1,大于的话就是使用2字节
    CHAR的存储因为是定长的不需要记录长度,如果只是存储Y/N之类的可以使用CHAR,CHAR只存储一个字节,而VARCHAR要两个字节
    与VARCHAR和CHAR相同的还有BINARY和VARBINARY,只是存储的是字节码,填充也不一样,会填充\0
    BLOB和TEXT
    如果BLOB和TEXT的长度太长的话会使用外部空间区域存储,在每个值会有个1-4字节存储一个指针,指向一个地址
    两者唯一的不同时BLOB存储二进制数据,没有排序规则和字符集,而TEXT又排序规则和字符集
    DATETIME和TIMESTAMP
    TIMESTAMP只能使用到2038年.MySQL提供了函数FROM_UNIXTIME()和UNIX_TIMESTAMP()函数转换Unix时间戳和时间
    BIT
    在MySQL5.0和更新版本中,这个是一个独立的数据类型.
    最长64位
    标识列
    尽量使用默认的int
    如果使用字符串类型,例如UUID,则可以使用UNHEX()函数转换为16字节的数字,并且存储在BINARY(16)中,检索时
    可以通过HEX()方法来格式化为十六进制格式
    特殊数据类型
    例如ip地址,可以使用函数INET_ATON()和INET_NTOA()进行转换

    数据库范式
    第一范式:属性的原子性
    第二范式:属性完全依赖于主键
    第三范式:
    第三范式要求一个数据库表中不包含已在其他表中已包含的非主关键字信息, 例如 存在一个课程表,课程表中有课程号(Cno),课程名(Cname),学分(Ccredit),那么在学生信息表中就没必要再把课程名,学分再存储到学生表中,这样会造成数据的冗余, 第三范式就是属性不依赖与其他非主属性,也就是说,如果存在非主属性对于码的传递函数依赖,则不符合第三范式

    这是自己学习过程中的笔记,如果有发现什么不对的地方,烦请各位指正.谢谢!

    相关文章

      网友评论

          本文标题:jdk读书笔记

          本文链接:https://www.haomeiwen.com/subject/ghytwhtx.html