整理笔记(线程和垃圾回收器)

2019-11-04  本文已影响0人  Chen_yiy

1. 集合部分.

集合有List,map,set

2.线程的状态

线程的状态分为:

3.线程池的优缺点

线程池的优点: 可以避免线程不断的创建和销毁来消耗cpu和时间.
线程如果执行的是不耗时的操作,那么线程执行比较快,线程是不会占用过多的cpu核数,只有在线程池执行的方法比较耗时才会占用其他的cpu核数,因此创建线程池大小的时候,对于线程池的大小选择尤为重要.
一般来说,线程池的大小是根据CPU的核数选择的大小

如果是CPU密集型,应该选择 cpu核数+1
如果是I/O密集型,应该选择CPU核数 * 2 + 1

因此可以得到另外的一个计算公式:
最佳线程数目 = ((线程等待时间+线程CPU时间)/ 线程CPU时间)

因此的出来的结论是 CPU执行时间越久的需要的线程数可以越多,CPU执行的时间越快,需要的线程大小应该越小,可以避免线程间切换引起的耗时和占用的资源

4.JVM的数据结构和垃圾回收算法

JVM的内存数据结构中划分为5个区域

4.1 可达性分析算法

由于对象的创建占用一定的内存的,当对象达到一定程度后,就必须要进行对象的回收.一般来说回收都是不在被程序引用的对象. 如何确认对象是否不被引用,主要是用根可达分析来计算对象是否被引用.

根可达分析算法:

  1. 根据"GC ROOT"来将所有有引用的对象形成一个引用链,那么在这些链以外的对象就是不可达的对象,也就是可以进行回收的对象.
    GC ROOT:也就是根,那么使用什么样的对象可以作为根?
  2. 虚拟机栈中引用的对象,为什么是虚拟机栈中的对象,可以想想虚拟机栈是什么?(正在运行的方法 虚拟机栈用包含的方法栈,方法出口,以及局部变量)
  3. 方法区中类的静态属性作为引用对象 (方法区中存放的是类加载信息,静态变量,都是和类对象相关的)
  4. 方法区中常量引用的对象. (都是类中的对象)
  5. 本地方法栈中JNI(native方法)引用的对象.

对象被宣布真正可以被回收是需要经过两个步骤

  1. 根据根可达分析后,没有在对应的引用链
  2. 看对象finalize方法,如果在对象的finaliz方法,重新建立引用,那么对象就无法被回收.

程序计数器算法
程序计数器: 对象在被创建后,当对象存在引用的时候,计数+1,对象引用使用完毕后,计数-1,当程序的计数为0时,就是可以被回收.
缺点: 如果是ABA引用,则没法办进行对象的回收

4.2垃圾回收算法

垃圾回收算法主要分为以下几种:

  1. 引用计数器回收算法:
    • 同引用计数算法: 如果对象的引用计数为0,则标记可以回收,但是但是如果循环引用的话,则无法回收.
  2. 标记清理算法:
    • 在开始进行清理的时候,先进行标记需要清理的对象,然后在回收对象,但是由于只清理对象,那么空间碎片就比较多.在开辟比较大的对象的空间的时候,很容易造成空间不足.
  3. 标记整理算法:
    • 标记整理算法整体思路和标记清理一样,就是在清理完对象后,把存活的对象统一向内存的一段移动.整理了内存碎片
  4. 复制算法:
    • 复制算法:在内存中划分为大小相等的两块区域,在通过根搜索确定到对应的存活的对象时,将存活的对象复制到新的内存区域中,然后把原来区域中的对象清理.存在的问题是:内存利用率比较低. 会比较频繁的进行对象回收.
  5. 分代收集算法:
    • 分代收集算法: 认为对象的存活的时间都比较短,因此将整个jvm划分为: 将内存区域划分为2个subver 区域和一个eden 区域 . 在内存的占比为1:1:8

    • 新生代:
      新生代的对象的生命周期比较短,因此需要频繁的进行对象的回收:新生代采用复制算法. 对象的创建主要在eden区,回收时,先将存活的对象复制到subver0区,然后清空eden区域,如果subver0也放满了对象,则将存活的对象复制到subver1中,然后清空subver0和eden区域的对象.这样保持subver两个区域的其中一个为空.
      当subver1也无法存放的下对象的时候,就会将存活的对象直接放置到老年代.如果老年代也满了,就触发一次fullGC,也就是新生代和老年代同时进行对象回收.

      - **注意:fullGC(stop the word 也就是执行fullGc的时候,是停止其他线程的). 新生代发生的GC是minorGC, minorGC发生的频率比较频繁,不一定等eden区满了才触发.**
      
    • 老年代(Tenured):
      老年带的对象都是经历过N次垃圾回收而仍然存活的,因此老年代中存储的都是存活周期比较久的对象.
      老年代的内存区域也比新生代的对象大很多,大概是1:2,当老年代满了之后就会触发fullGC fullGC发生的频率比较低.

    • 永久代(Perm):
      永久代的存放的对象都是些不会经历回收的对象(静态文件). 如果说java类,方法等.存放的是比如hibernate的一些类,因此需要设置一个比较大的持久类区域存放.永久代区也称为方法区. 方法区的对象回收是不同于堆区的回收的.

    • 方法区的回收主要的内容有: 废弃常量,和无用的类. 对于废弃常量可以通过根可达分析算法判断回收.

      • 对于无用的类需要满足以下条件才会被回收.
        a.该类的实例已经全部被回收.也就是不存在该类的任何实例.
        b.加载类的ClassLoader已经被回收.
        c.该类的java.lang.Class对象已经没有在任何地方被引用,无法再任何地方通过反射的方式访问该类.
      • 什么时候会触发FULLGC?
        a.老年代满了
        b.永久代满了
        c.system.gc方法调用,但是不是立即调用.
        d.在新生代的对象进入老年代内存时,老年代剩余空间不足
      • 压测时,fullGC频繁,需要怎么排查?
        思路: 对象创建的频繁,但是fullGC,说明新生代晋升到老年代的数量多,那么是不是新生代的对对象被引用,一直不被释放?
        a.观察GC日志,看是否有内存泄漏,或者代码存在一直引用对象的地方.
        b.调整老年代,新生代的区域
        c.Dump内存,分析

5.对象的内存布局

对象在内存中分为: 对象头、实例数据、对齐填充

5.1 对象头:[markword]

5.2 实例数据

5.3 对齐填充

5.4 四种锁变换的描述

java中包含的四种锁,无锁,偏向锁,轻量级锁,重量级锁,以及其他的锁 自旋锁 自适应自旋锁的过程描述

在这个过程中,首先判断对象的markword(对象头)是否处于可偏向的状态.

  1. 如果为可偏向状态,则通过CAS的方式,把自己的线程ID写入到对象头里.如果CAS成功,则认为获取到锁,开始执行同步代码.
  2. 如果不可偏向则检查markword中的线程ID是否等于当前线程的线程ID.
  3. 如果相等,则获取到锁,如果不等,则需要撤销偏向锁,也就是锁要升级了(但是此时需要等待全局安全带[此时间点,没有线程在执行字节码]),因为有另外的线程在争夺锁.此时需要注意的是偏向锁的撤销.
    • 偏向锁的撤销(需要的到达全局安全点):
      • 两个时机触发偏向锁的撤销
        1. 在通过CAS写入线程ID时,如果写入失败,则任务有其他线程争夺.
        2. 如果线程的对象的对象头里的偏向线程ID不是自己,则需要升级锁.
      • 偏向锁的撤销是在获取锁的过程中,发现了竞争,直接将一个偏向对象 升级到 轻量级锁的状态.
        首先到达全局安全点. 通过markword(对象头)中已经存在的那个线程ID,找到对应的那个线程,然后在该线程的栈帧上补充上轻量级锁时,会创建保存锁记录的空间,将对象头的mardword复制到锁记录,然后获取偏向锁对象的markword更新为指向该锁记录的指针.至此,阻塞在安全点上的线程可以继续执行.

5.5 盗图一个网上整理的对象头和锁之间转换的过程.

image.png

6.四种引用的作用和状态

6.1强引用

6.2软引用

6.3虚引用

6.4弱引用

7.GC收集器

stop the world

7.1 serial收集器

7.2 CMS 收集器

CMS收集器是老年代的收集器,CMS采用标记清理方式.在执行的过程中,主要有4个步骤. 初始标记.并发标记,重新标记,并发清理

7.3 G1收集器

上一篇下一篇

猜你喜欢

热点阅读