Java 7 Phaser

‘Java 7 Phaser’ http://www.oschina.net/question/12_35433 Java 7 引入了一个全新灵活的线程同步机制,名为 Phaser 。 如果你需要等待线程结束然后继续执行其他任务,那么 Phaser 是一个好的选择,接下来我们一步步来介绍 Phaser 的使用: 首先看下面的代码: import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.Phaser; public class PhaserExample { public static void main(String[] args) throws InterruptedException { List<Runnable> tasks = new ArrayList<>(); for (int i = 0; i < 2; i++) { Runnable runnable = new Runnable() { @Override public void run() { int a = 0, b = 1; for (int i = 0; i < 2000000000; i++) { a = a + b; b = a - b; } } }; tasks.add(runnable); } new PhaserExample().runTasks(tasks); } void runTasks(List<Runnable> tasks) throws InterruptedException { final Phaser phaser = new Phaser(1) { protected boolean onAdvance(int phase, int registeredParties) { return phase >= 1 || registeredParties == 0; } }; for (final Runnable task : tasks) { phaser.register(); new Thread() { public void run() { do { phaser.arriveAndAwaitAdvance(); task.run(); } while (!phaser.isTerminated()); } }.start(); Thread.sleep(500); } phaser.arriveAndDeregister(); } } 这个例子让我们可以深入了解 Phaser 的使用,下面是对这个代码的分析: ...

Created 27276-09-27 · Updated 9096-10-09 · 4 min · 807 words · -

Guava Multimap

Guava Multimap 在日常的开发工作中,我们有的时候需要构造像Map<K, List<V>>或者Map<K, Set<V>>这样比较复杂的集合类型的数据结构,以便做相应的业务逻辑处理。例如: import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.Test; public class MultimapTest { Map<String, List<StudentScore>> StudentScoreMap = new HashMap<String, List<StudentScore>>(); @Test public void testStudentScore(){ for(int i=10;i<20;i++){ StudentScore studentScore=new StudentScore(); studentScore.CourseId=1001+i; studentScore.score=100-i; addStudentScore("peida",studentScore); } System.out.println("StudentScoreMap:"+StudentScoreMap.size()); System.out.println("StudentScoreMap:"+StudentScoreMap.containsKey("peida")); System.out.println("StudentScoreMap:"+StudentScoreMap.containsKey("jerry")); System.out.println("StudentScoreMap:"+StudentScoreMap.size()); System.out.println("StudentScoreMap:"+StudentScoreMap.get("peida").size()); List<StudentScore> StudentScoreList=StudentScoreMap.get("peida"); if(StudentScoreList!=null&&StudentScoreList.size()>0){ for(StudentScore stuScore:StudentScoreList){ System.out.println("stuScore one:"+stuScore.CourseId+" score:"+stuScore.score); } } } public void addStudentScore(final String stuName,final StudentScore studentScore) { List<StudentScore> stuScore = StudentScoreMap.get(stuName); if (stuScore == null) { stuScore = new ArrayList<StudentScore>(); StudentScoreMap.put(stuName, stuScore); } stuScore.add(studentScore); } } class StudentScore{ int CourseId; int score; } 说明: 想 Map<String, List<StudentScore>> StudentScoreMap = new HashMap<String, List<StudentScore>>()这样的数据结构,自己实现起来太麻烦,你需要检查key是否存在,不存在时则创建一个,存在时在List后面添加上一个。这个过程是比较痛苦的,如果你希望检查List中的对象是否存在,删除一个对象,或者遍历整个数据结构,那么则需要更多的代码来实现。 ...

Created 18186-10-18 · Updated 9096-10-09 · 2 min · 287 words · -

maven plugins, pluginManagement

maven plugins, pluginManagement https://www.jianshu.com/p/49acf1246eff 1.plugins和pluginManagement的区别概述 plugins 和 pluginManagement 的区别,和我们前面研究过的 dependencies 和 dependencyManagement 的区别是非常类似的。plugins 下的 plugin 是真实使用的,而 pluginManagement 下的 plugins 下的 plugin 则仅仅是一种声明,子项目中可以对 pluginManagement 下的 plugin 进行信息的选择、继承、覆盖等。 2.pluginManagement使用实战 假如存在两个项目,项目A为项目B的父项目,其关系通过pom文件的关系确定。项目A的父pom文件片段如下: <pluginManagement> <plugins> <plugin> <groupId>``org.apache.maven.plugins</groupId> maven-source-plugin</artifactId> <version>2.1</version> <configuration> true</attach> </configuration> <executions> <execution> <phase>compile</phase> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin> </plugins> </pluginManagement> 如果项目B也想使用该plugin配置,则在项目B的子pom文件中只需要如下配置: <plugins> <plugin> <groupId>``org.apache.maven.plugins</groupId> maven-source-plugin</artifactId> </plugin> </plugins> 我们可以看到,子pom文件中,省去了版本、配置细节等信息,只需要指定groupId和artifactId,其他信息均从父pom文件继承。当然,如果子pom文件想定制自己的特定内容,可以另行设置,并会覆盖从父pom文件继承到的内容。 需要注意的是,dependencies 和 dependencyManagement 均是 project 下的直接子元素,但是 plugins 和 pluginManagement 却是 project 下 build 的直接子元素。 ...

Created 6066-03-06 · Updated 9096-10-09 · 1 min · 95 words · -

ConcurrentLinkedDeque

ConcurrentLinkedDeque 非阻塞线程安全列表——ConcurrentLinkedDeque应用举例 在java中,最常用的数据结构可能是列表。有数目不详的元素列表,你可以添加、阅读、或删除任何位置的元素。此外,并发列表允许不同的线程列表中添加或删除元素时不产生任何数据不一致。非阻塞列表提供如下操作,如果操作不能立即完成,列出抛出异常或者返回一个null值。Java 7中引入了ConcurrentLinkedDeque类,它实现了一个非阻塞并发列表,在本教程中,我们将学习使用这个类。 在这个例子中,我们将实现一个示例使用以下两个不同的任务: 一个将大量数据添加到一个列表中 一个大量地从同样的列表中删除数据 让我们为每个任务创建的线程: package com.howtodoinjava.demo.multithreading.concurrentLinkedDequeExample; import java.util.concurrent.ConcurrentLinkedDeque; public class AddTask implements Runnable { private ConcurrentLinkedDeque<String> list; public AddTask(ConcurrentLinkedDeque<String> list) { this.list = list; } @Override public void run() { String name = Thread.currentThread().getName(); for (int i = 0; i < 10000; i++) { list.add(name + ": Element " + i); } } } 和: ...

Created 23236-10-23 · Updated 9096-10-09 · 2 min · 306 words · -

Spring 静态注入的三种方式

Spring静态注入的三种方式 Spring静态注入的三种方式: (说明: MongoFileOperationUtil是自己封装的一个Mongodb文件读写工具类,里面需要依赖AdvancedDatastore对象实例,dsForRW用来获取Mongodb数据源) 在springframework里,我们不能@Autowired一个静态变量,使之成为一个spring bean,例如下面这种方式: @Autowired private static AdvancedDatastore dsForRW; 可以试一下,dsForRW在这种状态下不能够被依赖注入,会抛出运行时异常java.lang.NullPointerException,为什么呢?静态变量/类变量不是对象的属性,而是一个类的属性,spring则是基于对象层面上的依赖注入。 但是自己比较喜欢封装工具类,并通过@Component注解成功能组件,但是功能组件中的方法一般都是静态方法,静态方法只能调用静态成员变量,于是就有了下面的问题。封有的时候封装功能组件会需要底层的service注入,怎么办呢? 去网上搜了下解决办法,简单总结一下几种实现方式; 1.xml方式实现; <bean id="mongoFileOperationUtil" class="com.\*.\*.MongoFileOperationUtil" init-method="init"> <property name="dsForRW" ref="dsForRW"/> </bean> public class MongoFileOperationUtil { private static AdvancedDatastore dsForRW; private static MongoFileOperationUtil mongoFileOperationUtil; public void init() { mongoFileOperationUtil = this; mongoFileOperationUtil.dsForRW = this.dsForRW; } } 这种方式适合基于XML配置的WEB项目; 2.@PostConstruct方式实现; import org.mongodb.morphia.AdvancedDatastore; import org.springframework.beans.factory.annotation.Autowired; @Component public class MongoFileOperationUtil { @Autowired private static AdvancedDatastore dsForRW; private static MongoFileOperationUtil mongoFileOperationUtil; @PostConstruct public void init() { mongoFileOperationUtil = this; mongoFileOperationUtil.dsForRW = this.dsForRW; } } @PostConstruct 注解的方法在加载类的构造函数之后执行,也就是在加载了构造函数之后,执行init方法;(@PreDestroy 注解定义容器销毁之前的所做的操作) ...

Created 6066-02-06 · Updated 9096-10-09 · 1 min · 153 words · -

Maven Dependency Version 依赖版本自动升级

Maven Dependency Version 依赖版本自动升级 [1.15.0,2.0.0) x>=1.15.0 && x<2.0.0 依赖调节是为了解决版本不一致的问题(multiple versions),并采取就近原则(nearest definition)。 举例来说,A项目通过依赖传递依赖了两个版本的D: A -> B -> C -> ( D 2.0 ) , A -> E -> ( D 1.0 ) 复制代码那么最终A依赖的D的version将会是1.0,因为1.0对应的层级更少,也就是更近。 除了我们常用的<version>1.1.0</version>,在声明依赖版本的时候,可以通过表达式灵活地配置版本号。 例如配置jar包x的<version>标签支持如下几种语法: 1.0: 推荐依赖版本,此版本号可能会被覆盖。 复制代码还可以控制依赖的版本范围: (,1.0]: x <= 1.0 [1.2,1.3]: 1.2 <= x <= 1.3 [1.0,2.0): 1.0 <= x < 2.0 [1.5,): x >= 1.5 复制代码同时声明多个版本范围也是可以的,只要用逗号分隔即可: (,1.0],[1.2,): x <= 1.0 或者 x >= 1.2 复制代码当你不希望依赖某个版本的时候,可以声明为: (,1.1),(1.1,) 举例来说,你想依赖Apache的commons-math3的最新稳定版,而且不想每次有了新的版本手动去升级pom.xml,可以使用如下<version>配置: <dependency> <groupId>org.apache.commons</groupId> commons-math3</artifactId> <version>[3.+,)</version> </dependency> 复制代码编译项目,你会看到项目中依赖的版本为当前(2019/03/04)最新的正式版: 3.6.1。 ...

Created 3036-03-03 · Updated 9096-10-09 · 1 min · 109 words · -

Unsafe

“Unsafe” Unsafe在sun.misc 下,顾名思义,这是一个不安全的类,因为Unsafe类所操作的并不属于Java标准,Java的一系列内存操作都是交给jvm的,而Unsafe类却能有像C语言的指针一样直接操作内存的能力,同时也会带来了指针的问题。过度使用Unsafe类的话,会使出错率变得更大,因此官方才命名为Unsafe,并且不建议使用,连注释的没有。 而为了安全使用Unsafe,Unsafe类只允许jdk自带的类使用,从下面的代码中可以看出 public static Unsafe getUnsafe() { Class<?> caller = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(caller.getClassLoader())) throw new SecurityException("Unsafe"); return theUnsafe; } 如果当前Class是非系统加载的(也就是caller.getClassLoader()不为空),直接抛出SecurityException 。 在java9之后,又出现了一个jdk.internal.misc.Unsafe类,其功能与sun.misc.Unsafe类是一样的,唯一不一样的是在 getSafe() 的时候,jdk.internal.misc.Unsafe是没有做校验的,但是jdk包下的代码,应用开发时是不能直接调用的,而且在java9之后,两个Unsafe类都有充足的注释。 JUC紧密使用了Unsafe的功能。 功能简介 Unsafe类的功能主要分为内存操作、CAS、Class相关、对象操作、数组相关、内存屏障、系统相关、线程调度等功能。 内存操作 堆外(native memory)内存操作 //分配内存,并返回内存地址 public native long allocateMemory(long bytes); //扩充内存,address可以是allocateMemory方法返回的地址,bytes是扩充的大小 public native long reallocateMemory(long address, long bytes); //释放内存 public native void freeMemory(long address); //在给定的内存块设置默认值 public native void setMemory(long address, long bytes, byte value); //获取指定地址值的byte类型 public native byte getByte(long address); //设置堆外指定值的byte类型的值 public native void putByte(long address, byte x); 堆内内存操作 ...

Created 20206-09-20 · Updated 9096-10-09 · 3 min · 456 words · -

Exception Handling

异常捕捉的确是对性能有影响的,那是因为一旦异常被抛出,函数也就跟着 return 了。而程序在执行时需要处理 函数栈 的上下文,这会导致性能变得很慢,尤其是线程栈比较深的时候。但从另一方面来说,异常的抛出基本上表明程序的错误。程序在绝大多数情况下,应该是在没有异常的情况下运行的, 所以,有异常的情况应该是少数的情况,不会影响正常处理的性能问题。 对于我们并不期望会发生的事,我们可以使用异常捕捉;对于我们觉得可能会发生的事,使用返回码。 https://time.geekbang.org/column/article/675 你觉得自己是一个Java专家吗?是否肯定自己 已经全面掌握了Java的异常处理机制?在下面这段代码中,你能够迅速找出异常处理的六个问题吗? 1 OutputStreamWriter out = ... 2 java.sql.Connection conn = ... 3 try { // ⑸ 4 Statement stat = conn.createStatement(); 5 ResultSet rs = stat.executeQuery( 6 "select uid, name from user"); 7 while (rs.next()) 8 { 9 out.println("ID: " + rs.getString("uid") // ⑹ 10 ",姓名: " + rs.getString("name")); 11 } 12 conn.close(); // ⑶ 13 out.close(); 14 } 15 catch(Exception ex) // ⑵ 16 { 17 ex.printStackTrace(); //⑴,⑷ 18 } 作为一个Java程序员,你至少应该能够找出两个问题。但是,如果你不能找出全部六个问题,请继续阅读本文。 ...

Created 23236-03-23 · Updated 9096-10-09 · 6 min · 1166 words · -

相互转换逗号分隔的字符串和 List

“相互转换逗号分隔的字符串和List” https://blog.csdn.net/yywusuoweile/article/details/50315377 将逗号分隔的字符串转换为List 方法 1: 利用JDK的Arrays类 String str = "a,b,c"; List<String> result = Arrays.asList(str.split(",")); 方法 2: 利用Guava的Splitter String str = "a, b, c"; List<String> result = Splitter.on(",").trimResults().splitToList(str); 方法 3: 利用Apache Commons的StringUtils (只是用了split) String str = "a,b,c"; List<String> result = Arrays.asList(StringUtils.split(str,",")); 方法 4: 利用Spring Framework的StringUtils String str = "a,b,c"; List<String> str = Arrays.asList(StringUtils.commaDelimitedListToStringArray(str)); 将List转换为逗号分隔符 方法 1: 利用JDK (好像没有很好的方法,需要一步一步实现) NA 方法 2: 利用Guava的Joiner List<String> list = new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); String str = Joiner.on(",").join(list); 方法 3: 利用Apache Commons的StringUtils ...

Created 9096-10-09 · Updated 9096-10-09 · 1 min · 130 words · -

Spring IoC,依赖注入

IoC(Inversion of Control,控制反转)是 Spring 的核心机制。它把"创建和管理对象"的控制权从应用代码交给 Spring 容器,应用代码只需声明依赖,容器负责提供。Spring Framework 整体定位见 Spring。 IoC 的具体实现方式叫 DI(Dependency Injection,依赖注入),容器通过构造器、字段或 Setter 方法将依赖对象注入进来。 IoC 解决了什么问题 传统写法中,对象自己负责创建依赖: // 传统写法:自己创建依赖,紧耦合 public class OrderService { private UserDao userDao = new UserDaoImpl(); // 自己 new,换实现要改代码 private EmailService email = new EmailService("smtp.example.com", 465); } 这带来三个问题: 紧耦合:OrderService 直接依赖具体实现类,换实现必须修改源码 难以测试:无法替换成 Mock 对象,单元测试困难 对象生命周期散乱:每次 new 都产生新对象,无法复用单例 IoC 写法: // IoC 写法:声明需要什么,容器负责提供(推荐构造器注入) @Service public class OrderService { private final UserDao userDao; private final EmailService emailService; public OrderService(UserDao userDao, EmailService emailService) { this.userDao = userDao; this.emailService = emailService; } } 切换实现时只需修改容器配置(或替换实现类上的 @Primary / @Qualifier),OrderService 代码本身不用动。 ...

Created 12126-05-12 · Updated 11116-08-11 · 9 min · 1753 words · -

Spring Boot Startup Callbacks: 应用启动后执行逻辑

若要在 Spring 应用上下文已就绪 之后执行一段初始化代码,常见做法如下。多个 Bean 可同时实现同一类接口,使用 @Order 或 Ordered 控制顺序。框架总览见 Spring Boot。 ApplicationRunner 与 CommandLineRunner 二者都会在 SpringApplication 启动流程末尾被调用。需要结构化解析命令行参数(如 key=value 形式)时,优先使用 ApplicationRunner(入参为 ApplicationArguments);只关心原始字符串数组时用 CommandLineRunner 即可。 @Component @Order(1) public class SeedDataRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) { // post-startup logic } } @Component @Order(2) public class BannerRunner implements CommandLineRunner { @Override public void run(String... args) { // post-startup logic } } @PostConstruct 在某个 Spring Bean 完成依赖注入之后、对外使用前执行,适合该 Bean 自身的轻量初始化。不要在 static 方法上使用 @PostConstruct(规范要求实例方法)。 ...

Created 26266-06-26 · Updated 11116-08-11 · 1 min · 135 words · wiloon

Spring Bean Lifecycle: 从实例化到销毁

Spring 容器管理的不只是"创建对象",还包括从实例化到销毁的完整过程。容器概览见 Spring IoC;本文聚焦单个 singleton Bean 从 new 到被销毁经历的各个阶段。 整体流程 graph TD A[实例化调用构造器] --> B[属性填充依赖注入] B --> C[Aware 接口回调] C --> D[BeanPostProcessor前置处理] D --> E["初始化回调PostConstruct → afterPropertiesSet → init-method"] E --> F[BeanPostProcessor后置处理AOP 代理在此生成] F --> G[就绪可用] G --> H[容器关闭] H --> I["销毁回调PreDestroy → destroy → destroy-method"] singleton Bean 只走一次这套完整流程;prototype Bean 只做到"就绪可用",容器交出对象后就不再管理,销毁阶段(H、I)不会发生——细节见 Spring 中 Bean 的作用域。 实例化与属性填充 容器先按 BeanDefinition 里记录的构造器(或工厂方法)创建原始对象,再做属性填充(即依赖注入,见 Spring IoC §依赖注入的三种方式)。这两步是循环依赖唯一可能出现的阶段——对象已存在但还没准备好被别人安全引用。 三级缓存与循环依赖 以 A 依赖 B、B 又依赖 A(字段注入或 Setter 注入场景)为例: sequenceDiagram participant C as Container participant A as Bean A participant B as Bean B C->>A: new A()(原始对象,属性未填) C->>C: A 的 ObjectFactory 放入三级缓存 C->>A: 注入属性,发现需要 B C->>B: new B()(原始对象) C->>C: B 的 ObjectFactory 放入三级缓存 C->>B: 注入属性,发现需要 A C->>C: 查 A:一级 miss,二级 miss,三级 hit C->>C: 调用 ObjectFactory 得到 A 的早期引用 C->>C: 早期引用移入二级缓存,移除三级缓存 C->>B: 注入 A 的早期引用,B 创建完成 C->>C: B 放入一级缓存 C->>A: 用完成态的 B 完成属性注入,A 创建完成 C->>C: A 放入一级缓存,清理二三级缓存里的临时数据 容器创建 A:new A() 已经创建出 A 的真实原始对象(构造器已执行完毕,只是属性还没注入)。Spring 把「如何暴露这个已存在的原始对象」包装成一个 ObjectFactory(本质是个 () -> getEarlyBeanReference(...) 的 lambda,调用它才会决定返回原始对象还是 AOP 代理),再把这个工厂本身放进三级缓存 singletonFactories,并把 A 标记为「正在创建中」。也就是说,三级缓存里存的是「怎么拿到早期引用」的工厂,不是对象本身;对象在 new 这一步就已经真实存在了。 给 A 注入属性时发现需要 B,转去创建 B,同理:new B() 创建出真实的原始对象,再把它的 ObjectFactory 放进三级缓存。 给 B 注入属性时发现需要 A:依次查一级(没有,A 还没做完)、二级(没有)、三级缓存——命中 A 的 ObjectFactory,调用它拿到 A 的早期引用,挪进二级缓存并从三级缓存移除。 B 用这个早期引用完成注入、创建完成,放入一级缓存。 流程回到 A:用刚创建好的 B 完成属性注入,A 也创建完成,放入一级缓存。 三级缓存各自存的到底是什么(一级、二级缓存的底层结构见 Spring IoC §单例缓存怎么存): ...

Created 11116-08-11 · Updated 11116-08-11 · 3 min · 597 words · -

jstack

背景 jstack 用来查看某个 Java 进程内的线程堆栈信息,语法: jstack [option] PID jstack [option] executable core jstack [option] [server-id@]remote-hostname-or-ip 常用参数: 参数 说明 -l long listing,额外打印锁信息;发生死锁时可用 jstack -l pid 观察锁持有情况 -m mixed mode,同时输出 Java 堆栈和 C/C++(Native 方法)堆栈 -F 当 jstack [-l] pid 无响应时强制 dump 现代 JDK(含 JDK 26)里还有替代吗 有,而且是官方推荐的替代:jcmd <pid> Thread.print。 jstack、jmap、jinfo 这几个独立小工具早已被 Oracle 标记为 experimental / unsupported,官方故障排查文档现在统一推荐通过 jcmd 这个诊断入口来做线程栈、堆 dump、JFR 等操作: jcmd <pid> Thread.print # equivalent to: jstack <pid> jcmd <pid> Thread.print -l # equivalent to: jstack -l <pid>, with lock info jstack 目前仍随 JDK 26 一起提供,还能正常使用,只是不建议在新脚本 / 新流程里继续依赖它。参数与用法详见 jcmd;更完整的生产诊断工具对比(Arthas、async-profiler、JFR 等)见 生产环境诊断工具选型。 ...

Created 11116-11-11 · Updated 16166-07-16 · 4 min · 759 words · -

DestroyJavaVM 与 JVM 内置线程

现象 用 jcmd <pid> Thread.print 查看 Java 进程的线程 dump 时,经常能看到几个"内置"线程,其中 DestroyJavaVM 最容易让人误以为是 bug 或线程泄漏: DestroyJavaVM:负责在程序退出时卸载 JVM 的线程,大部分时间都在等待,直到虚拟机真正关闭。 Signal Dispatcher:处理操作系统发给 JVM 的原生信号。 Finalizer:从 finalization 队列取出对象并调用其 finalize 方法。 Reference Handler:高优先级线程,负责将待处理的 Reference 加入队列,定义在 java.lang.ref.Reference。 DestroyJavaVM 为什么会"一直挂着" 这是因为大多数应用不只有 main 一个线程,还会额外创建其它线程。 所有 POJO 应用都从 main 方法开始执行。最简单的情况下,main 方法会完成所有工作:创建对象、调用方法等。一旦 main 执行完毕,JVM 会启动一个 DestroyJavaVM 线程来关闭虚拟机,这个线程会等待所有线程都执行完毕后才真正做关闭的工作,以确保你创建的线程都能跑完再销毁 JVM。 而带 GUI 的应用通常会运行一组线程:一个监听键盘、鼠标等系统事件,一个维护窗口和显示等。这类应用的 main 方法可能只是启动所需的线程后就退出了。它同样会创建 DestroyJavaVM 线程,但这时该线程要做的就是等待所有已创建的线程完成后才关闭虚拟机。 因此,任何创建了线程并依赖这些线程完成工作的应用,都会有一个 DestroyJavaVM 线程在等待它们结束。由于它做的只是 join 其他所有运行中的线程,本身不会消耗额外资源。 现代 JDK(包括 JDK 26)里还存在吗 存在。DestroyJavaVM 是 JNI Invocation API 里虚拟机启动 / 关闭机制的一部分,从早期 JDK 到当前的 HotSpot 都没有变过,线程 dump 里依然能看到这个线程名。 ...

Created 30306-03-30 · Updated 16166-07-16 · 1 min · 144 words · -

JVM Memory Layout and Tuning Notes

说明 本文是一篇较早的 JVM 调优笔记,原文涵盖的堆栈基础、GC 算法、分代模型、G1 设计目标等内容,站内已有更完整、持续维护的独立文章: GC 分代模型、回收算法、收集器与发展时间线 → Java GC HotSpot 启动参数(堆、分代比例、GC 日志、收集器选择等)→ HotSpot JVM 启动参数 堆、栈、方法区基础概念 → Java 堆栈方法区 强引用 / 软引用 / 弱引用 / 虚引用 → Java 引用类型 生产环境诊断工具(Arthas、async-profiler、JFR 等)→ Java 生产环境诊断工具选型 本文只保留原文中站内暂未覆盖的部分:对象内存布局与大小计算、一份历史调优配置示例、内存泄漏排查思路,以及关于 JVM 内存设计的一点延伸思考。 Java 对象的内存布局与大小 一个空 Object 对象在堆中的大小是 8 byte,这只是对象头的大小,不包含任何字段: Object ob = new Object(); 这行代码实际占用 4 byte + 8 byte:4 byte 是栈中保存引用所需的空间,8 byte 是堆中对象头的信息。因为所有 Java 非基本类型对象都默认继承 Object,所以任何 Java 对象的大小都必然大于 8 byte。 有了空对象的大小,可以推算其他对象的大小,例如: class NewObject { int count; boolean flag; Object ob; } 其大小为:空对象大小(8 byte)+ int(4 byte)+ boolean(1 byte)+ Object 引用(4 byte)= 17 byte。由于 JVM 对对象内存分配按 8 字节对齐,实际占用会向上取整到 24 byte。 ...

Created 14146-04-14 · Updated 16166-07-16 · 2 min · 227 words · -

Java GC

分代模型 名词解释 在 GC 的世界里,对象指的是通过应用程序利用的数据的集合,是 GC 的基本单位,一般由头(header)和域(field)构成。 活动对象:能通过引用程序引用的对象,即可以直接或间接从全局变量空间中引出的对象 非活动对象:不能通过程序引用的对象,即被清除的目标 垃圾(Garbage):系统运行过程中产生的无用对象,这些对象占据一定的内存空间,如果长期不被释放,可能导致 OOM 垃圾收集器(Garbage Collector):负责回收垃圾对象的组件 垃圾回收(Garbage Collect):垃圾收集器工作时对垃圾进行回收的动作 新生代(Young Generation) 所有新生成的对象首先都放在新生代,新生代的目标是尽可能快速地收集掉那些生命周期短的对象。 新生代内存按照 8:1:1 的比例分为一个 Eden 区和两个 Survivor 区(Survivor0、Survivor1)。大部分对象在 Eden 区中生成。回收时先将 Eden 区存活对象复制到 Survivor0 区,然后清空 Eden 区;当 Survivor0 区也存放满时,则将 Eden 区和 Survivor0 区的存活对象复制到 Survivor1 区,然后清空 Eden 区和 Survivor0 区,此时 Survivor0 区为空,再将 Survivor0 区和 Survivor1 区角色互换,如此往复。 当 Survivor1 区不足以存放 Eden 区和 Survivor0 区的存活对象时,就将存活对象直接存放到老年代。若老年代也满了,就会触发一次 Full GC,即新生代、老年代都进行回收。 新生代发生的 GC 也叫 Minor GC,发生频率比较高(不一定要等 Eden 区满了才触发)。 老年代(Tenured Gen,Old Generation) 在新生代中经历了 N 次垃圾回收后仍然存活的对象,会被放到老年代中,因此老年代中存放的都是生命周期较长的对象。 老年代内存比新生代大很多(大概比例是 1:2),当老年代内存满时触发 Major GC 即 Full GC。Full GC 发生频率比较低,因为老年代对象存活时间长、存活率高。 持久代(Permanent Generation)与 Metaspace 持久代用于存放静态文件,如 Java 类、方法等。持久代对垃圾回收没有显著影响,但有些应用可能动态生成或调用一些 class(例如 Hibernate),这种情况下需要设置一个比较大的持久代空间来存放运行过程中新增的类。 ...

Created 4046-06-04 · Updated 14146-07-14 · 7 min · 1293 words · -

IdentityHashMap:基于引用相等的 Map 实现

java.util.IdentityHashMap 自 JDK 1.4 起存在,表面上实现了 Map 接口,但官方文档明确说它不是通用 Map 实现——判等方式和内部存储结构都和 HashMap 不同,只应该用在明确需要"引用相等"语义的少数场景。 与 HashMap 的核心区别:引用相等而非逻辑相等 HashMap 判断两个 key 是否相同,用的是 equals();IdentityHashMap 用的是 ==,也就是判断是否是同一个对象引用。计算哈希值时也不调用 key 自身的 hashCode(),而是统一用 System.identityHashCode(key)。 Map<String, Integer> hashMap = new HashMap<>(); Map<String, Integer> identityMap = new IdentityHashMap<>(); String a = new String("key"); String b = new String("key"); hashMap.put(a, 1); hashMap.put(b, 2); System.out.println(hashMap.size()); // 1,a.equals(b) 为 true,视为同一个 key identityMap.put(a, 1); identityMap.put(b, 2); System.out.println(identityMap.size()); // 2,a != b,视为两个不同的 key 这个差异不仅体现在 key 上,entrySet()、keySet()、values() 这些视图集合里元素的比较,同样是引用相等语义,而不是 HashMap 视图那种基于 equals() 的语义。 ...

Created 13136-07-13 · Updated 13136-07-13 · 2 min · 391 words · -

HashMap

历史背景 HashMap 最早在 JDK 1.2(1998 年发布的 Java 2)引入,是 java.util 集合框架(Collections Framework)随 1.2 一起推出的一部分,与 ArrayList、HashSet、Collection/Map 接口等同批加入; 在此之前,JDK 1.0/1.1 只有 Hashtable——线程安全但不允许 null key/value。HashMap 是作为 Hashtable 的非同步、允许 null 的替代品引入的,因此在没有并发访问需求的场景下性能更好;本文后续讨论的实现细节(JDK 1.7 数组 + 链表、JDK 1.8 引入红黑树)都是在这个基础上的持续演进。二者更详细的差异对比见 HashMap 与 Hashtable 的区别。 JDK 1.7 默认容量为 16,负载因子为 0.75。Map 在使用过程中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容,而扩容这个过程涉及到 rehash、复制数据等操作,所以非常消耗性能。 因此通常建议能提前预估 HashMap 的大小最好,尽量的减少扩容带来的性能损耗。 put 方法 public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; } put 方法执行流程: ...

Created 14146-01-14 · Updated 13136-07-13 · 15 min · 2999 words · -

java basic

java basic JDK 和 JRE 的区别 JRE:Java Runtime Environment 的简称,java 运行时环境,为 java 的运行提供了所需环境。 Java 虚拟机 jfr JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。 包含了 JRE 编译器: javac 其他的工具: JavaDoc,Java调试器(jdb), jcmd, jstat, jmap JDK 其实包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具。简单来说:如果你需要运行 java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,需要安装 JDK。 http://www.wiloon.com/jdk-jre == vs equals == 对于基本类型和引用类型 == 的作用效果是不同的 基本类型:比较的是值是否相同; 引用类型:比较的是引用是否相同; String x = "string"; String y = "string"; String z = new String("string"); System.out.println(x==y); // true System.out.println(x==z); // false System.out.println(x.equals(y)); // true System.out.println(x.equals(z)); // true 代码解读:因为 x 和 y 指向的是同一个引用,所以 == 也是 true,而 new String()方法则重写开辟了内存空间,所以 == 结果为 false,而 equals 比较的一直是值,所以结果都为 true。 ...

Created 20206-09-20 · Updated 9096-07-09 · 4 min · 715 words · -

HashMap 遍历: entrySet vs keySet

两种遍历方式 HashMap 常见的遍历方式有两种:entrySet() 和 keySet()。两者最终都能拿到全部键值对,但性能有差别。 entrySet(推荐) Map<String, String> map = new HashMap<>(); Iterator<Map.Entry<String, String>> iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry<String, String> entry = iter.next(); String key = entry.getKey(); String val = entry.getValue(); } entry 中已经同时持有 key 和 value,取值时不需要再回查一次 map。 keySet Map<String, String> map = new HashMap<>(); Iterator<String> iter = map.keySet().iterator(); while (iter.hasNext()) { String key = iter.next(); String val = map.get(key); // extra lookup for every key } keySet() 只拿到 key 的集合,遍历时还要对每个 key 再调用一次 map.get(key) 才能取到 value。 ...

Created 2026-04-02 · Updated 4046-07-04 · 1 min · 210 words · -