第一部分 走进java

展望java技术的未来

1. 模块化

2. 混合语言

每个应用层使用不同的语言,各个语言之间交互不存在任务困难,就像使用自己语言的原生API一样方便。因为他们最终都运行在同一个虚拟机上。

  • 发展方向: java语言的虚拟机-————>>>> 多语言虚拟机

    3.面向函数式编程

    第二部分 自动内存管理机制

    第2章 java内存区域与内存溢出异常

    面试题:
    实例对象是怎样存储的?
    答: 对象的实例存储在堆空间、对象的元数据存在方法区(元数据区),对象的引用存在栈空间。
    image.png
    image.png
    一块区域用来存数据, 一块区域用来跑数据。
    存数据: 方法区 、 堆
    跑数据:虚拟机栈、 本地方法栈、 程序计数器

    2.1程序计数器

    程序计数器:是一块较小的内存空间,他可以看做是当前线程所执行的字节码的行号指示器。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
    java虚拟机的多线程: 是通过线程轮流切换并分配处理器执行时间的方式来实现的。每条线程都需要有一个独立的程序计数器,各条线程之间互不影响,独立存储。
    如果线程正在执行一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空(Undefined)。此内存区域是java虚拟机规范中唯一一个没有规定任何OutOfMemoryErroy情况的领域。

    2.2java虚拟机栈

    虚拟机栈:描述的是java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作树栈、动态链接、方法出口等信息。入栈——>>出栈: 每一方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。

    • 动态扩展栈深度,可能会有栈溢出、内存溢出等异常。

      2.3本地方法栈

      本地方法栈与虚拟机栈所发挥的作用是非常相似的。
      区别: 虚拟机栈为虚拟机执行的java方法(也就是字节码)服务。而本地方法栈则为虚拟机使用到的native方法服务。

    • 动态扩展栈深度,可能会有栈溢出、内存溢出等异常。

      2.4 java 堆

      定义: 是java虚拟机所管理的内存中最大的一块。是被所有线程共享的一块内存区域,在虚拟机启动时创建。
      唯一目的: 存放对象的实例。几乎所有的对象实例都在这里分配内存。
      java堆也是垃圾收集器管理的主要区域,因此很多时候也叫做”GC堆”。从内存回收的角度来看,现在收集器基本都是采用分代收集算法,所以java堆中还可以细分为:新生代和老年代。
      java堆可以处理物理上不连续的内存空间中,只要逻辑上是连续的即可,就想我们的磁盘空间一样。 在实现时,既可以是固定大小,也可以是扩展的。 当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx 和-Xms控制)。

      2.5方法区

      与java堆一样,是线程共享的内存区域,它用于存储已被虚拟机加载的 类信息、常量(final)、静态变量(static)、及时编译器编译后的代码等数据。

      2.5.1运行时常量池

      运行时常量池: 是方法去的一部分。

      第3章 垃圾收集器与内存分配策略

      java内存中虚拟机栈、本地方法区、程序计数器 三个区域随线程而生,随线程而死;但是java堆、方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,也是垃圾收集器所关注的部分。

      3.1 引用计数算法

      判断对象存活: 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能被使用的。即可以回收的对象。

  • 优点: 实现简单、效率高;

  • 缺点: 很难解决对象之间相互引用的问题。 也是java虚拟机里面没有选用引用计数法来管理内存的原因。

    3.2 可达性分析算法

    主流的商用程序语言(Java、C#)的主流实现中,都是称通过可达性分析来判断对象是否存活。
    这个算法的基本思路是:通过一系列的称为”GC Roots“的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GCRoots没有任何引用链相连,说明这个对象不可达,即可以被回收。

  • java语言中,可作为GC Roots的对象包括以下几种:

    • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
    • 方法区中类静态属性引用的对象。
    • 方法区中 常量引用的对象。
    • 本地方法栈中JNI (即一般说的Native方法)引用的对象。
  • 引用

我们希望有这样一类对象: 当内存空间还足够时,则保留在内存中;如果内存空间在进行垃圾回收之后还非常紧张,则可以抛弃这些对象。 很多系统的缓冲都符合中央的应用场景。

  • 引用分类:

    • 强引用
    • 软引用
    • 弱引用
    • 虚引用

      3.3垃圾收集算法

      3.3.1 标记 - 清除算法(Mark-Sweep)

      先标记出要回收的对象, 再统一回收所有被标记的对象。
  • 缺点:

    • 效率低:标记和清除两个过程的效率都不高。
    • 空间碎片问题:标记-清除后会产生大量的不连续的内存碎片,空间碎片太多可能会导致以后的程序运行过程中需要分配较大对象时, 无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

      3.3.2 复制算法

      为了解决 效率问题,“复制算法”出现了。它将可用内存按容量划分为大小相等的两块,每次值使用其中的一块,当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后在把已经使用过的内存一次清理掉。
  • 缺点:

    • 将内存缩小为原来的一半。 空间利用率太低。

      3.3.3 标记整理算法、

      标记过程跟标记-清除算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有的存活对象对向一端移动,然后直接清理掉端边界以外的内存;
  • 优点: 提升了空间利用率;

  • 缺点:效率还是低;

    3.3.4 分代收集算法

    当前商业虚拟机的垃圾收集都是采用“分代收集”算法,这种算法只是根据对象存活周期的不同将内存划分为几块。 一般被java对分为新生代跟老年代,这样就可以根据各个年代的特点采用最适当的收集算法。 在新生代中,每次垃圾收集时都会发现有大批对象死去,只有少量存活,那就可以选用:“复制算法”,只需要付出少量存活对象的复制成本就可以完成收集。 而老年代中因为对象存活率高,没有额外空间对它进行分配担保,就必须使用“标记-清除”或者“标记-整理”算法来进行回收。
    image.png
    image.png

问题:

  1. Minor GC 与 Full GC 区别?
    1. 新生代(Minor GC):指发生在新生代的垃圾收集动作,因为java对象大多都具备朝生夕死的特性, 所以Minor GC 非常频繁,一会回收速度也比较快。
    2. 老年代GC(Major GC/ Full GC):指发生在老年代的GC, Major GC的速度一般比较Minor GC慢10倍以上。
  2. 老年代放置什么对象?
    1. 大对象 直接进老年代(很长的字符串、数组等);
    2. 长期存活的对象。多次GC 没有被回收的对象。

第三部分 虚拟机执行子系统

第6章 类文件结构

代码编译的结果 从本地机器码转变为字节码,是存储格式发版的一小步,确实编译语言发展的一大步。

6.1 无关性的基石

Java 虚拟机不和包含Java在内的任何语言绑定, 他只与”Class”文件,这种特定的二进制文件格式所关联,Class文件中包含了Java虚拟机指令集和符号表以及若干其他辅助信息。 Java虚拟机并关心Class的来源是何种语言。
image.png

6.2 Class类文件的结构

6.3 Class 类加载机制

image.png

image.png
image.png

6.4 JVM内存模型

image.png
image.png

第四部分 程序编译与代码优化

第10章 早期(编译器)优化

1.编译过程大概分为以下三个过程:

  1. 解析与填充符号表 过程;
  2. 插入式注解处理器的注解处理过程;
  3. 分析与字节码生成过程。
  4. image.png

    2. 注解处理器:

    可以看做是一组编译器的插件,在这些插件里面,可以读取、修改、添加抽象语法书中的任意元素。

    3. 语法糖

    语法糖 :可以看做是编译器实现的一些小把戏,可以提供我们的效率,但是也有坑。
    语法糖包含: 泛型、自动装箱、自动拆箱、增强for循环 、 变长参数、条件编译(if不成立的分支会在编译时被擦除掉。) 等等。

    3.1 泛型檫除坑:

    image.png

    3.2 自动装箱、拆箱

    ```java public class DD { public static void main(String[] args) {

    1. Integer a = 1;
    2. Integer b = 2;
    3. Integer c = 3;
    4. Integer d = 3;
    5. Long g = 3L;
    6. System.out.println(c.equals(d));
    7. System.out.println(c == (a + b));
    8. System.out.println(c.equals(a + b));
    9. System.out.println(g == (a + b));
    10. System.out.println(g.equals(a + b));

    } }

// 输出结果: “C:\Program Files\Java\jdk1.8.0_202\bin\java.exe” “-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2018.3.1\lib\idea_rt.jar=57446:D:\Program Files\JetBrains\IntelliJ IDEA 2018.3.1\bin” -Dfile.encoding=UTF-8 -classpath “C:\Program Files\Java\jdk1.8.0_202\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_202\jre\lib\rt.jar;F:\Exercise\study\jdk-resourceo-study\out\production\jdk-resourceostudy” com.zhiqiang.server.basic.DD true true true true false

Process finished with exit code 0

— 总结:

  1. 包装类的 == 在不遇到 ‘运算符’的情况下不会自动拆箱。 ```

第五部分 高效并发

由于计算机的运算速度与它的存储和通信子系统速度的差距太大,大量的时间都花费在了磁盘的I/O、网络通信或者数据库访问上。如果不希望处理器在大部分时间里都处于等待其他资源的状态,就必须使用一些手段把处理器的运算能力“压榨”出来,否则就造成大量的浪费,而让计算机同时处理几项任务则是最容易想到、也被证明是非常有效的“压榨”手段。——————java多线程
除了充分利用 计算机处理器的能力外, 一个服务端同时对多个客户端提供服务则是另一个更具体的并发应场景。———————高并发
衡量一个服务性能的高低好坏,每秒事务处理树(TPS),是最重要的指标之一。
高效率:程序线程 并发协调的越有条不紊,效率自然就会越高;
低效率:线程间频繁阻塞甚至死锁,将会大大降低程序的并发能力;

第12章 Java内存模型与线程

由于计算机的存储设备与处理器的运算速度 有几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存(Cache)来作为内存与处理器之间的缓存平;将用户需要使用到的数据复制到缓存中,让运算能尽快进行,当运算结束后再从缓存同步回内存之中。
image.png

12.1 Java内存模型(Java Memory Model,JMM)

image.png

12.2 内存间交互操作

主内存<——>工作内存 之间的交互。java内存模型中定义了以下8中操作来完成,虚拟机是现实必须保证下面提及的每一种操作都是原子的、不可再分的。

  • lock (锁): 作用于主内存的变量,他把一个变量标识为一条线程独占的状态;
  • unlock(解锁): 作用于主内存变量,它把一个处于锁定状态的变量释放出来, 释放后的变量才能被其他线程锁定。
  • read(读取): 作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
  • load(载入): 作用于工作内存中的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用): 作用于工作内存中的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
  • assign(赋值) : 作用于工作内存的变量,他把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节指令时执行这个操作。
  • store (存储): 作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
  • write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

12.3 原子性、可见性、有序性

  1. 原子性: java中同步块就是—synchronized 关键字
  2. 可见性: 指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。

—— volatile 、synchronized、final 关键字

  • 普通变量与volatile变量的区别

volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此,可以说volatile保证了多线程操作时变量的可见性, 而普通变量不能保证这一点。

  1. 有序性: Java程序中天然有序性总结一句话为: 如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。 前半句指的是:“线程内表现为串行的语义”,后半句指的是:“指令重排序”现象和“工作内存与主内存同步延迟”现象。 -——volatile 、synchronized
  • 总结:synchronized 是万能的,但是滥用会导致性能下降。

第13章 线程安全与锁优化

13.1 线程的生命周期

深入理解java虚拟机学习笔记 - 图15

13.2 Java锁

image.png

13.2.1 CAS 算法:

CAS,即 Compare And Swap(比较与交换),是一种无锁算法,基于硬件原语实现,能够在不使用锁的情况下实现多线程之间的变量同步。jdk中的java.util.concurrent.atomic包中的原子类就是通过CAS来实现了乐观锁。
个人理解:就像修改密码时, 会让你先输入之前的密码,这个就是其中的比较, 你输入的旧密码 跟 数据库中现在的密码 进行比较,如果相同则表示可以修改, 然后输入新密码,这样就可以更新成功。其中自旋就是你一直重试密码,可能重试几次就好了, 阻塞就好比找回密码,又是输入邮件或手机号等验证码等等比较繁琐一些。 所以问题是:如果实在想不起来密码, 重试次数多了就不好, 不如找回密码。】
image.png

  • 自旋锁(乐观锁): 为了避免线程 切换(会导致上下文切换:指线程1执行未完成—>执行线程2,执行完成—>再次执行线程1)浪费太多时间, 因此使用自旋 进行load主内存中的值, 然后比较及更新。若不相同继续自旋更新。 可以使用命令设置自旋次数。
    • ABA问题:在JDK内部已经解决, 使用版本号可以解决。
    • CPU开销大:CAS算法需要不断地自旋来读取最新的内存值,长时间读取不到就会造成不必要的CPU开销。
    • 线程阻塞、线程切换(上下文切换原理解析):
      • image.pngimage.png
  • 自适应自旋: JDK1.7 后自旋锁的参数被取消。虚拟机不在支持配置自旋锁,由虚拟机自动调整(根据上一次自旋次数上下浮动)。

  • 13.2.2 隐式锁(synchronized)、显示锁(ReentrantLock)

  • 悲观锁:synchronized与实现了 Lock的所有类。



  • 13.2.3 AQS: 抽象队列同步器 (AbstractQueuedSynchronizer)

    公平锁: 先到先得。 先来的线程先排队。等前面的线程释放锁,然后排队的第一个进行获取锁。
    非公平锁:就是不排队, 先插队直接去尝试过去锁,拿到了就获取锁,拿不到在进行排队。
    image.png
    条件队列只能在 独占锁 中进行。 不能在共享锁进行。