代码之旅

I love Coding !

上篇博客是线程池的源码分析,这篇是关于延时线程池的源码分析(JDK1.8)。

ScheduledThreadPoolExecutor继承了ThreadPoolExecutor类,实现了ScheduledExecutorService接口。ScheduledThreadPoolExecutor 支持延后执行给定task,或是定期执行。任务在到时间后执行,但是没有任何的实时保证。对于计划执行时间相同的任务,会按照提交顺序,先进先出执行。

阅读全文 »

PermGen是Permanent Generation的缩写,它是HotSpot对于JVM方法区的一种实现,也常被称为永久代。永久代是一片连续的堆空间,在JVM启动之前通过在命令行设置参数-XX:MaxPermSize来设定永久代最大可分配的内存空间,32位JVM的默认最大内存大小为64 MB,64位版本为82 MB。永久代的垃圾收集是和老年代(old generation)在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。

一个常见的问题是,当JVM加载的类信息容量超过了参数-XX:MaxPermSize设定的值时,应用将会抛出OOM异常。特别地是,在Java 7之前,字符串常量池实际上保留在PermGen里,这种情况导致的java.lang.OutOfMemoryError: PermGen space,常会让人产生误解。

移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。

阅读全文 »

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。
阅读全文 »

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

阅读全文 »

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

阅读全文 »

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

  1. xml配置文件中,添加事务管理器bean配置
1
2
3
4
5
6
7
<!-- 事务管理器配置,单数据源事务 -->
<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")注解.
  • 标注在类前:标示类中所有方法都进行事务处理
  • 标注在接口、实现类的方法前:标示方法进行事务处理
阅读全文 »

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

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

Stream 是 Java 8 新特性,可对 Stream 中元素进行函数式编程操作,例如 map-reduce。Stream 表示一个序列,其中的元素支持串行或并行的聚合方法。

1
2
3
4
int sum = widgets.stream()
.filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();
阅读全文 »

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

阅读全文 »

Cpu-Cache和伪共享

缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节。CPU每次从主存中拉取数据时,会把相邻的数据也存入同一个cache line。

一个Java的long类型变量是8字节,因此在一个缓存行中可以存8个long类型的变量。在访问一个long数组的时候,如果数组中的一个值被加载到缓存中,它会自动加载另外7个。因此你能非常快的遍历这个数组。事实上,你可以非常快速的遍历在连续内存块中分配的任意数据结构。

阅读全文 »