Victor's Code Journey
Victor's Code Journey

Java虚拟机-SafePoint

A point in program where the state of execution is known by the VM。

HotSpot的安全点定义: Safepoint 是程序执行过程中的一个点,在这个点,所有的GC根都是已知的,所有的堆对象内容都是一致的。从全局角度来看,所有的线程都必须在安全点阻塞,然后GC才能运行。(作为一种特殊情况,运行JNI代码的线程可以继续运行,因为它们只使用句柄。在安全点期间,它们必须阻塞而不是加载句柄的内容。)从局部角度来看,安全点是代码块中的一个特别点,执行线程可能会在该点阻塞GC。大多数执行点都符合安全点的条件。每个安全点都有强不变量,这些不变量在非安全点可能会被忽略。

总而言之,Safepoint 指在 Java 虚拟机中,程序执行时的一个特殊点。在 Safepoint 处,所有的线程都会被暂停下来,以便进行JVM 的一些特定的操作1。例如:

  • 定时进入 SafePoint:经过-XX:GuaranteedSafepointInterval配置的时间,都会让所有线程进入 Safepoint,一旦所有线程都进入,立刻从 Safepoint 恢复。这个定时主要是为了一些没必要立刻 Stop the world 的任务执行,推荐设置-XX:GuaranteedSafepointInterval=0关闭这个定时
  • 由于 jstack,jmap 和 jstat 等命令,也就是 Signal Dispatcher 线程要处理的大部分命令,都会导致 Stop the world:这种命令都需要采集堆栈信息,所以需要所有线程进入 Safepoint 并暂停。
  • 偏向锁取消(这个不一定会引发整体的 Stop the world,参考 JEP 312: Thread-Local Handshakes):Java 认为,锁大部分情况是没有竞争的(某个同步块大多数情况都不会出现多线程同时竞争锁),所以可以通过偏向来提高性能。即在无竞争时,之前获得锁的线程再次获得锁时,会判断是否偏向锁指向我,那么该线程将不用再次获得锁,直接就可以进入同步块。但是高并发的情况下,偏向锁会经常失效,导致需要取消偏向锁,取消偏向锁的时候,需要 Stop the world,因为要获取每个线程使用锁的状态以及运行状态。
  • Java Instrument 导致的 Agent 加载以及类的重定义:由于涉及到类重定义,需要修改栈上和这个类相关的信息,所以需要 Stop the world。
  • Java Code Cache 相关:当发生 JIT 编译优化或者去优化,需要 OSR 或者 Bailout 或者清理代码缓存的时候,由于需要读取线程执行的方法以及改变线程执行的方法,所以需要 Stop the world
  • GC:这个由于需要每个线程的对象使用信息,以及回收一些对象,释放某些堆内存或者直接内存,所以需要 Stop the world
  • JFR 的一些事件:如果开启了 JFR 的 OldObject 采集,这个是定时采集一些存活时间比较久的对象,所以需要 Stop the world。同时,JFR 在 dump 的时候,由于每个线程都有一个 JFR 事件的 buffer,需要将 buffer 中的事件采集出来,所以需要 Stop the world。

Server端图片导出

最近的工作需要在服务端生成报表图片,Java库生成的图片实在是惨不忍睹,酷炫的还是要看JS😂。解决方案是: 服务数据 + html模板 + headless浏览器。

Java 模块化

JDK 从Java 9开始 在包之上引入了一个新的抽象级别,正式名称为 Java 平台模块系统 (JPMS),简称“模块”。Java 模块是一种打包机制,使您能够将 Java 应用程序或 Java API 打包为单独的 Java 模块。Java 模块被打包为模块化 JAR 文件。Java 模块可以指定它包含哪些 Java 包,这些包应该对使用此模块的其他 Java 模块可见。Java 模块还必须指定完成其工作所需的其他 Java 模块。

Spring Transactional

Spring @Transactional的配置方式有两种:

  1. xml配置文件中,添加事务管理器bean配置
<!-- 事务管理器配置,单数据源事务 -->
    <bean id="myTransactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="myDataSource" />
    </bean>
<!-- 使用annotation定义事务 -->
    <tx:annotation-driven transaction-manager="myTransactionManager" />
  1. 在使用事务的方法或者类上添加 @Transactional("myTransactionManager")注解.
  • 标注在类前:标示类中所有方法都进行事务处理
  • 标注在接口、实现类的方法前:标示方法进行事务处理

Java并发之CompletableFuture

Future接口可以构建异步应用,但依然有其局限性。它很难直接表述多个Future 结果之间的依赖性。实际开发中,我们经常需要达成以下目的:

  • 将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个又依赖于第一个的结果。
  • 等待 Future 集合中的所有任务都完成。
  • 仅等待 Future集合中最快结束的任务完成(有可能因为它们试图通过不同的方式计算同一个值),并返回它的结果。
  • 通过编程方式完成一个Future任务的执行(即以手工设定异步操作结果的方式)。
  • 应对 Future 的完成事件(即当 Future 的完成事件发生时会收到通知,并能使用 Future 计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结果)

Java并发之原子变量Atomic

Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。

CPU Memory Cache

曾今计算机稳定的基本结构悄然改变,硬件开发人员开始致力于优化单个子系统。于是电脑一些组件的性能大大的落后因而成为了瓶颈。由于开销的原因,大容量存储器和内存子系统相对于其他组件来说改善得更为缓慢。

大容量存储的性能问题往往靠软件来改善: 操作系统将常用(且最有可能被用)的数据放在主存中,因为后者的速度要快上几个数量级。或者将缓存加入存储设备中,这样就可以在不修改操作系统的前提下提升性能。(然而,为了在使用缓存时保证数据的完整性,仍然要作出一些修改).

而解决内存的瓶颈更为困难,它与大容量存储不同,几乎每种方案都需要对硬件作出修改。目前,这些变更主要有以下这些方式:

  • RAM的硬件设计(速度与并发度)
  • 内存控制器的设计
  • CPU缓存
  • 设备的直接内存访问(DMA)

java:随机数

计算机科学中的随机数分为两种:

  • 真随机数: 其生成过程是不可预测、不可重复的,完全基于自然界中物理过程的随机性。
  • 伪随机数: 其生成过程是确定性的、可预测的,但通过一个算法使得生成的数字序列看起来像是随机的。
    • 使用一个确定的数学公式。它从一个初始值(称为“种子”)开始,通过算法计算出下一个数,然后用这个数作为新的内部状态,继续计算下一个数,如此反复。

Java并发之并发优先线程池

Java原生线程池在提交任务时会优先将线程数扩展到 CoreSize,然后会将任务入队,在队列塞满后会尝试继续扩展线程数到MaxSize。这种方式适合cpu密集型任务,而且任务时间不宜过长,否则会造成队列里面任务的堆积。

对于 RPC 通信场景的 IO 密集型任务,这种调度方式就不太合适。更适合并发优先的调度策略,即优先扩展线程数到 MaxSize。然后再尝试入队。要是实现上面的调度策略需要基于 JDK 原生线程池做一下调整。