JDK,JRE,JVM


JAVA

JVM,JRE,JDK

  1. JVM

  • 类加载子系统

  • 运行时数据区

  • 栈(线程)

    • 栈的先进后出符合函数的调用顺序
    • 局部变量表:存储函数体内的局部变量。
    • 操作数栈:临时存储操作数,例如a=1,先将1压入操作数栈中,局部变量表中添加a变量,将操作数栈栈顶元素赋值给a。
    • 程序计数器:记录下条指令的位置,每个线程都会有程序计数器,为了上下文切换能够找到加载的位置。
    • 本地方法栈:native修饰的方法存储

    • full GC :老年区满了做full GC,范围:整个堆和方法区。
    • JVM调优
      • 分析核心业务流程。
      • 调整年轻代和老年代内存大小。
      • 尽量减少Full GC
  • 执行引擎
  1. JRE
  • java运行时环境
    • 包括JVM和java内库:math,Logging等。
  1. JDK
  • 包括JRE,命令行工具javac等,IDE等。

JMM-java线程内存模型

  • JMM数据原子操作

volatile

并发编程三大特性:可见性,原子性,有序性

volatile保证可见性和有序性,但原子性需要借助synchronized这样的锁机制

  • Volatile可见性

    • volatile作用
      • 避免指令重排(单例模式)
      • 保持内存可见性
    • 多线程测试代码
    public class VolatileTest {
        private static volatile boolean initFlag=false;
    
        public static void main(String[] args) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Wailting ...");
                    while (!initFlag){
    
                    }
                    System.out.println("=========success");
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    prepareDate();
                }
            }).start();
        }
    
        private static void prepareDate() {
            System.out.println("prepareing ...");
            initFlag=true;
            System.out.println("prepare end ...");
        }
    }
    
    • 代码执行流程

    • volatile可见性底层实现
      • 底层实现主要是通过汇编Lock前缀指令。他会锁定这块内存区域的缓存,并写回主内存
      • IA-32对Lock的解释
        • 将当前处理器的缓存行的数据立即写回到系统内存
        • 写回操作会引起其他其他CPU里缓存该内存地址的数据无效(MESI协议)

  • volatile 原子性问题

    • 多线程对num=0进行num++操作。会出现某次操作丢失的情况。结果小于等于正常情况
    public class VolatileTest2 {
        public static volatile int num=0;
        public static  void increase(){
            num++;
        }
    //创建10个线程,每个线程进行1000次num++
        public static void main(String[] args) throws InterruptedException {
            Thread[] threads=new Thread[10];
            for (int i =0;i<threads.length;i++){
                threads[i]=new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for( int i=0;i<1000;i++){
                            increase();
                        }
                    }
                });
                threads[i].start();
            }
            for (Thread t:threads) {
                t.join();
            }
            System.out.println(num);
        }
    }
    // <=10000

HashMap

  • 数组+链表+红黑数(java8加入)

    • 数组方便按下标查询,链表避免了数组插入和删除
  • put操作

    • 如果key为null,放到Entry[0]的位置
    • 对key值hash计算,在对hash值取模确定数组中的index
    • 如果index位置发送冲突
      • java7,头插法插入链表
      // 方法一,jdk1.8 & jdk1.7都有:
      static final int hash(Object key) {
           int h;
           return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
      }
      // 方法二,jdk1.7有,jdk1.8没有这个方法,但是实现原理一样的:
      static int indexFor(int h, int length) {
           return h & (length-1); //位运算效率高 
      }123456789
    • 这里的Hash算法本质上就是三步:
      (1) 取key的hashCode值,h = key.hashCode();
      (2) 高位参与运算,h ^ (h >>> 16);
      (3) 取模运算,h & (length-1)。
  • 扩容机制

    • 当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容。

      HashMap的容量由一下几个值决定:

       int threshold;             // 扩容阈值 
       final float loadFactor;    // 负载因子
       transient int modCount;  // 出现线程问题时,负责及时抛异常
       transient int size;     // HashMap中实际存在的Node数量
       HashMap的容量size乘以负载因子[默认0.75] = threshold;  // threshold即为开始扩容的临界值
      

      当HashMap中的元素个数超过数组threshold时,就会进行数组扩容, 数组默认大小为16,那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩大一倍,然后重新计算每个元素在数组中的位置。

      HashMap不是无限扩容的,当达到了实现预定的MAXIMUM_CAPACITY,就不再进行扩容。

    • 负载因子为什么是0.75?

      • 空间与时间的均衡。
  • 初始容量是2的指数次幂?

    • 方便取模和扩容,同时减少冲突。
  • java7线程不安全?

    • 数据丢失
    • 死锁,多线程rehash时可能会发生死锁

    深度截图_选择区域_20200317074833

    • java8取消了rehash的操作
      • 添加了loHead,loTail和hiHead,hiTail。
  • 链表转红黑数

    • 链表长度达到8时,先判断数组长度是否大于64,大于:转变红黑树,小于:扩容

线程底层原理

  • ULT,KLT(java用)
  • 线程生命周期

  • 线程池的优势
    • 重用存在的线程,减少线程的创建,消亡的开销
    • 提高响应速度,任务到达时不需要创建线程就可以执行。
    • 提高可管理性。

Author: Maelsee
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Maelsee !
评论
 Previous
设计模式 设计模式
桥接模式(Bridge) 介绍:将抽象与实现分离开来,使它们可以独立变化。 关系图: 将品牌和类型分开。 品牌接口 public interface Brand { void info(); } 实现品牌接口 publ
2020-03-06 Maelsee
Next 
shell 学习笔记 shell 学习笔记
shell 知识总结用户在命令行输入命令后,一般情况下Shell会fork并exec该命令,但是Shell的内建命令例外,执行内建命令相当于调用Shell进程中的一个函数,并不创建新的进程。以前学过的cd、alias、umask、exit、
2020-02-24 Maelsee
  TOC