在编写一个系统的时候,我们总是希望我们的系统在设计上具备较好的可维护性和可扩展性,当客户需求有变,或者需要增加新功能时,能够从容应对,而一些前人总结的设计原则可以让我们在遇到这样的情况时候,不至于被动,从而能够以尽可能小的工作量来实现客户的需求。
利用享元模式解决内存大量细粒度对象
最近在看之前一个自己写的项目代码的时候,发现之前构造的责任链像个楼梯台阶一样的堆在那里,很是影响代码的美观性,并且一条链上的七、八个对象在每次请求时都需要创建一遍,对于一个高并发的项目来说,是一笔不小的开销,于是想对这一块的代码进行优化,而享元模式刚好满足我的需求。
享元模式(Flyweight)是 以共享的方式有效地支持大量的细粒度对象 。能做到共享的关键是区分 内部状态(Internal State) 和 外部状态(External State) 。
OAuth 2.0 协议原理与实现:Token 生成策略
OAuth2.0 协议定义了授权详细流程,并最终以 token 的形式作为用户授权的凭证下发给客户端,客户端后续可以带着 token 去请求资源服务器,获取 token 权限范围内的用户资源。
对于 token 的描述,OAuth 2.0 协议只是一笔带过的说它是一个字符串,用于表示特定的权限、生命周期等,但是却没有明确阐述 token 的生成策略,以及如何去验证一个 token。RFC6749 对于 access token 的描述:
The client obtains an access token – a string denoting a specific scope, lifetime, and other access attributes.
OAuth 2.0 协议原理与实现:协议原理
OAuth 2.0 协议是一种三方授权协议,目前大部分的第三方登录与授权都是基于该协议的标准或改进实现。OAuth 1.0 的标准在 2007 年发布,2.0 的标准则在 2011 年发布,其中 2.0 的标准取消所有 token 的加密过程,并简化了授权流程,但因强制使用 HTTPS 协议,被认为安全性高于 1.0 的标准。
探秘 JVM:监控与诊断工具
JDK 在给提供基础的 java 依赖库的同时,也在 bin 目录下提供了一系列的小工具,除了我们常用的 java 和 javac 以外,还包含许多对 JVM 进行性能监控和故障诊断的工具。这些工具能够为我们日常程序开发和问题排查提供极大的便利,主要包含以下几种:
工具 | 描述 |
---|---|
jps | 显示系统运行中的 JVM 进程列表 |
jstat | 用于收集 JVM 各方面的运行数据(类加载、GC、JIT 编译等) |
jinfo | 查看和编辑 JVM 配置信息(JVM 启动参数、系统环境变量等) |
jmap | 生成 JVM 的堆转储快照(heapdump 文件) |
jhat | 用于分析 jmp 命令生成的 heapdump 文件,它会建立一个 HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果 |
jstack | 生成 JVM 的线程转储快照(通常所说的 threaddump 文件或 javacore 文件),用于判断是否存在死锁、死循环,以及阻塞等情况 |
jconsole | 可视化 java 监视与管理控制台,基于 JMX |
jvisualvm | 多合一故障处理工具,jconsole 的增强版,自 JDK 9 开始不再随 JDK 默认提供,需要独立下载,地址:http://visualvm.github.io/ |
探秘 JVM:编译与优化
以 java 语言为例,JVM 针对 java 程序的优化可以发生在编译期和运行期,相应的优化操作分别发生在 javac 编译器在将 java 源程序编译成字节码期间,以及运行时即时编译器(JIT: Just In Time Compiler)将字节码编译成本地机器码期间。此外,还有一类编译器可以将源程序直接编译成与目标机器指令集相关的二进制代码,此类编译器称为提前编译器。运行期依赖于 JIT 的编译优化的主要目的在于提升程序的执行效率,而编译期优化的主要目的在于支持 java 语法糖,提升语言的易用性和编码效率。如果以字节码所处的位置作为参考线,那么编译期的编译可以称为前端编译,而即时编译和提前编译合起来则可以称为后端编译。
探秘 JVM:字节码执行引擎
在不同的虚拟机实现中,执行引擎在执行 java 代码时可能会有 解释执行 (通过解释器执行)和 编译执行 (通过 JIT 生成本地代码执行)两种选择,也可能是二者兼备,但不管采用哪种方式执行,当我们调用一个方法的时候,都需要确定目标方法的具体版本,因为在面向对象语言中存在封装、继承和多态的三大特性,一个方法可能因为重载或覆盖而存在多个版本。
方法调用阶段的主要工作是确定被调用方法的版本,而非执行具体的方法。字节码文件中存储的是方法的符号引用,只有将符号引用解析成直接引用(运行时方法的入口内存地址)才能确定具体调用的方法是谁,这个映射的过程有的发生在类加载的解析阶段,有的则发生在运行期间。
探秘 JVM:类加载机制
JVM 的类加载机制描述了类数据从字节码文件加载到内存,并对其进行校验、解析、初始化,并最终成为能够直接被 JVM 使用的 java 数据类型的过程。
类的整个生命周期可以分为 加载、连接(验证、准备、解析)、初始化、使用、卸载 5 个阶段(加载、连接和初始化构成了类加载全过程),其中连接又可以细分为验证、准备、解析 3 个阶段。如下图所示:
探秘 JVM:字节码文件结构与指令
探秘 JVM:垃圾收集机制
JVM 中的程序计数器、java 虚拟机栈、本地方法栈均属于线程私有,其生命周期控制在线程生命周期范围内,并且 java 虚拟机栈和本地方法栈中的的栈帧会随着对应方法的执行而出栈和入栈,由生到灭,所以这些区域的内存使用是确定性的(编译期已知)。然而,Java 堆和方法区是线程共享的,对象的创建也是动态的,所以垃圾收集的主战场集中在 java 堆和方法区。
相对于 java 堆而言,方法区的垃圾收集性价比要低很多,JVM 规范甚至不强制要求 JVM 在方法区实现垃圾收集。不过对于复杂应用,尤其是大量使用反射、动态代理、CGLib 等字节码框架,动态生成 JSP,以及 OSGi 这类频繁自定义类加载器的场景中,对于方法区实现垃圾收集还是有必要的。方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型。对于一个类型是否不再被使用,JVM 在判定时需要同时满足以下 3 个条件:
- 该类所有的实例都已经被回收了,也就是说堆中不存在该类及其派生子类的任何实例。
- 加载该类的类加载器已经被回收了。
- 该类对应的 Class 类对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。