虚拟机
编译
-
早期编译(优化)
-
解析与填充符号表过程
- 词法分析-->Token序列
- 语法分析-->抽象语法树
- 填充符号表
注解处理器
-
语义分析与字节码生成
-
标注检查
- 变量使用前声明
- 变量赋值类型匹配
- 常量折叠
-
数据及控制流分析
- 局部变量使用前赋值
- 方法每条路径具有返回值
- 所有受查异常被正确处理
解语法糖
-
字节码生成
将<<init>()和<<clinit>()方法添加进语法树
-
-
-
运行期(优化)
解释器
-
编译器
- Client Compiler(C1)
- Server Compoler(C2)
-
分层编译
- 第0层,程序解释执行,解释器不开启性能监控功能
- 第1层,C1编译,将字节码编译成本地代码,进行简单、可靠的优化,如有必要将加入性能监控逻辑
- 第2层,C2编译,将字节码编译成本地代码,启用耗时较长的优化,甚至根据性能监控信息进行一些不可靠的激进优化
编译触发:基于计数器的热点探测
类加载
-
加载
“全盘负责委托机制”:
全盘负责,是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入;
委托机制,是指先委托父类装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并装载目标类。(可防止用户修改JRE基础类库)
-
链接
验证
-
准备
为类变量(static 变量)分配内存并设置初始值(零值)
-
解析
将常量池的符号引用替换为直接引用
-
初始化
执行<<clinit>方法(static{}块和类变量赋值动作)
-
类加载器
-
Bootstrap ClassLoader
- <JAVA_HOME>\lib
-
Extension ClassLoader
- <JAVA_HOME>\lib\ext
-
Application ClassLoader
- 用户类路径(ClassPath)
-
-
Tomcat
AppClassLoader load $CATALINA_HOME/bin/bootstrap.jar with CommonClassLoader
-
CommonClassLoader load $CATALINA_HOME/lib
This class loader contains additional classes that are made visible to both Tomcat internal classes and to all web applications.
-
WebApp1\2... load /WEB-INF/classes;/WEB-INF/lib
当处理从Web应用程序的WebappX类加载器加载类的请求时,该类加载器将首先在本地存储库中查找,而不是在查找之前进行委托。有例外。作为JRE基类的一部分的类不能被覆盖。
同一个tomcat中,每个web app分别有一个WebAppClassLoader,通过覆盖loadclass()方法不做加载委托,实现各个web app类相互不影响。
-
热加载
同一个classLoader不能加载相同名字的class,tomcat实现热加载,通过卸载当前web context,重新实例化一个WebAppClassLoader,重新加载class。
方法调用
-
解析调用
- 编译期就确定方法,类加载阶段,将符号引用转换成直接引用 static,private,父类方法
-
分派
-
静态分派
- 编译阶段,根据传递参数的静态类型调用方法,方法重载
- 多分派,根据参数类型和方法接收类确定
-
动态分派
- 运行期,根据传递参数的动态类型调用方法,方法重写
- 单分派,在编译阶段确定方法签名,运行期只需确定方法实际接收类
-
-
解析
- 发生在类加载解析阶段
-
静态分派
- 发生在编译阶段,由编译器根据参数静态类型确定调用方法
Java内存结构
-
线程私有
PC(当前线程执行字节码行号指示)
-
虚拟机栈
-
栈帧
-
局部变量表(方法参数、局部定义变量)
- 基本数据类型
- 引用
操作数栈
动态连接
方法出口
-
-
-
线程共享
-
堆
-
普通对象
-
对象头
Java程序的对象头固定占8字节(32位系统)或12字节(64位系统默认开启压缩, 不开压缩为16字节)
- Mark Word 锁状态、GC年龄等
- 类型指针
- 数组长度(如果是数组)
实例数据(包括父类和子类字段)
补齐填充(补齐为8位整数倍)
-
-
-
方法区
Java7之前存在,由于字符串等被放入运行时常量池,导致一系列OOM问题,Java8将其挪到本地内存,大小可动态增长。
-
Class对象-作为访问方法区入口(HotSpot)
- 静态变量
-
Class字节码
魔数
-
常量池
这里是指Class字节玛结构中的一部分。
内存中存在运行时常量池,jdk6.0之前存在与方法区中,jdk7.0存在于堆中,jdk8之后被挪到了本地内存中。
- 符号/直接引用
类或接口的访问标志(public、abstract等)
类、父类、接口索引
字段表集合
-
方法表集合
-
Code属性
-
max_locals
- this+exception params + 本地变量s
max_stack
-
-
-
-
直接内存(如NIO的DirectByteBuffer)
GC
safepoint:方法返回前、方法call后、循环末尾跳转、异常跳转
-
OopMap
- 在safepoint处插入指令,收集GC roots
-
GC Roots
- 寄存器引用、全局变量、栈帧中本地变量(活着的)
遍历对象,调用finalize
-
GC算法
-
Stop The World
-
正在运行的普通线程
-
Safe Point
位置:方法返回前、方法call后、循环末尾跳转、异常跳转
-
实现
-
OopMap
- 作用:收集GC Roots,将栈内的引用偏移,以及寄存器中存放的引用,记录到OopMap数据结构
- 位置:每个类有自己的OopMap,在类加载时生成,记录类中引用的偏移;
-
Stop用户线程:轮询
-
解释执行
- JVM设置一个2字节的dispatch tables,轮询这个dispatch tables
-
JIT
- test指令:JVM将一个特定的内存页置为不可读,通过test指令将线程挂起
-
-
-
-
正在运行native code的线程
当VM thread看到一个Java线程在执行native code,它不需要等待这个Java线程进入阻塞状态,因为当Java线程从执行native code返回的时候,Java线程会去检查safepoint看是否要block(When returning from the native code, a Java thread must check the safepoint _state to see if we must block)
感觉和safe region颇相似。
-
阻塞的线程
-
Safe Region
线程阻塞时,标识自己进入safe region,此时该线程引用关系不会变化,JVM可安全的标记;
线程被唤醒时,先检查是否已经完成根节点枚举(或是完成整个GC,视不同垃圾回收器而定),如果完成,则继续执行,否则挂起知道收到可以安全离开safe region的信号为止。
-
-
-
遍历对象
- GC Roots
- finalize
- 标记垃圾对象
-
清理垃圾对象
-
新生代:基于复制
- Eden
- from Survivor
- to Survivor
老年代:基于标记清除、标记整理
-
-
-
垃圾收集器
-
新生代
触发MinorGC(Young GC)
虚拟机在进行minorGC之前会判断老年代最大的可用连续空间是否大于新生代的所有对象总空间 1、如果大于的话,直接执行minorGC 2、如果小于,判断是否开启HandlerPromotionFailure,没有开启直接FullGC 3、如果开启了HanlerPromotionFailure, JVM会判断老年代的最大连续内存空间是否大于历次晋升的大小,如果小于直接执行FullGC 4、如果大于的话,执行minorGC
-
Serial
-
参数
- -XX:SurvivorRatio
- -XX:PretenureSizeThreshold
- -XX:HandlePromotionFailure
组合:CMS、Serial Old
-
-
ParNew
-
参数
- -XX:SurvivorRatio
- -XX:PretenureSizeThreshold
- -XX:HandlePromotionFailure
- -XX:ParallelGCThreads
组合:CMS、Serial Old
-
-
Parellel Scavenge
特点:关注吞吐量 用户代码运行时间/(GC + 用户... )
-
参数
- -XX:MaxGCPauseMillis(通过调整新生代大小,比如调小,时间减少,但GC更频繁,吞吐量下降)
- -XX:GCTimeRatio
- -XX:+UseAdaptiveSizePolicy(开启后,JVM自动调整新生代大小,SurvivorRatio,晋升老年代年龄等参数实现最大吞吐量)
组合:Serial Old、Parallel Old
-
-
老年代
触发FullGC
老年代空间不足
如果创建一个大对象,Eden区域当中放不下这个大对象,会直接保存在老年代当中,如果老年代空间也不足,就会触发Full GC。为了避免这种情况,最好就是不要创建太大的对象。
持久代空间不足
如果有持久代空间的话,系统当中需要加载的类,调用的方法很多,同时持久代当中没有足够的空间,就出触发一次Full GC
YGC出现promotion failure
promotion failure发生在Young GC, 如果Survivor区当中存活对象的年龄达到了设定值,会就将Survivor区当中的对象拷贝到老年代,如果老年代的空间不足,就会发生promotion failure, 接下去就会发生Full GC.
统计YGC发生时晋升到老年代的平均总大小大于老年代的空闲空间
在发生YGC是会判断,是否安全,这里的安全指的是,当前老年代空间可以容纳YGC晋升的对象的平均大小,如果不安全,就不会执行YGC,转而执行Full GC。
显示调用System.gc
-
CMS
特点:关注响应时间
过程:初始标记,并发标记,重新标记,并发清理
-
CPU占用问题
并发清理阶段,线程数默认为 (CPU数量+3/4),即占用不少于25%的CPU资源,当CPU数量降低时占用更多。
-
浮动垃圾问题:并发清理阶段,预留空间给用户线程使用,产生的垃圾下次GC处理,若空间不足,使用Serial Old重新回收Old区,导致停顿时间过长
为了在并发清理阶段不STW,需要预留部分内存用于并发清理阶段的用户线程使用,JDK1.5默认在老年代68%时启动并发清理,JDK1.6提升为92%,通过-XX:CMSInitiatingOccupancyFraction可设置。当预留的内存不足时,会出现一次“Concurrent Mode Failure”,使用Serial Old会暂停用户线程重新收集old区。
垃圾碎片问题:CMS采用“标记-清除”算法,可能存在空间足够,却没有连续可用的空间存放对象,触发full gc,可通过参数控制full-gc时整理内存碎片
Serial Old(MSC)
Parallel Old
-
-
G1
-
回收周期
年轻代GC
-
混合GC
触发时机:
当老年代分区占用总堆比例超过阈值(默认45%)时,触发混合GC。
初始标记:和下一次年轻代GC一起,STW并行标记,收集所有的GC Roots。
并发标记:多线程并发协同标示存活对象图。
重新标记:STW并行重新标记上个阶段产生的新垃圾。
并行回收:垃圾清理。
-
-
- Full GC
触发时机:对象分配失败时?
使用串行垃圾收集器对整个堆全面压缩。
G1的设计目标通过不断调优而不再需要full GC。
- 堆空间调整
JVM通过在Xms和Xmx之间动态调整堆大小及年轻代大小,以满足用户设置的GC暂停时间MaxGCPauseMillis和GCTimeRatio(用户线程时间/GC线程时间)的目标。
- RSet
- 引用关系
- PRT 粒度?
- 关系维护
维护时机:
对象引用关系变化时(包括引用赋值、GC移动对象等),触发写栅栏代码,维护Rset。
若发生一个跨区引用关系变化,G1垃圾收集器会将相应的card加入到“脏卡片队列”。“并发优化线程”会扫描队列中的卡片来更新RSet。当“并发优化线程”来不及处理不过来时,会挂起用户线程,让用户线程也加入到更新Rset。
- 全局卡片表
在任意收集周期,扫描Rset与PRT时,会将扫描到的引用记录标记到全局卡片表,避免重复扫描。在收集周期的最后将该表清空,显示为Clear CT。
- 工作窃取机制
- 收集活动图
深入并发
Java内存模型
-
模型简图
-
内存操作
- lock&unlock
- use&load&read
- assign&store&write
-
happens before
- 用途:呈现给程序员的并发视图,阐述操作之间的内存可见性。
-
与JMM的关系
-
问题:重排序
目的:提高执行效率
-
类型
-
编译器重排
JMM “happens before” 规则确保编译器禁止特定的编译器重排。
例子:局部变量和操作数栈的关系
-
普通读写/volatile读写
- 当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后
- 当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前
- 当第一个操作是volatile写,第二个操作是volatile读时,不能重排序
-
final域
-
写
- JMM禁止编译器把final域的写重排序到构造函数之外(确保在return之前)
-
读
- none
-
-
处理器重排序
-
类型
-
指令并行重排
- 指令并行执行,改变没有数据依赖性的指令执行顺序
-
内存系统重排
- 例子:cpu缓存的执行结果,未及时同步到内存,导致顺序错乱
-
-
内存屏障
-
类型
-
LoadLoad
- 序列:Load1,Loadload,Load2
- 作用:确保Load1所要读入的数据能够在被Load2和后续的Load指令访问前读入
-
StoreStore
- 序列:Store1,StoreStore,Store2
- 确保Store1的数据在Store2以及后续Store指令操作相关数据之前对其它处理器可见(例如向主存刷新数据
-
LoadStore
- 序列: Load1; LoadStore; Store2
- 确保Load1的数据在Store2和后续Store指令被刷新之前读取
-
StoreLoad
- 序列: Store1; StoreLoad; Load2
- 确保Store1的数据在被Load2和后续的Load指令读取之前对其他处理器可见
”Enter”与”Load”相同,”Exit”与”Store”相同,除非被原子指令的使用和特性覆盖
-
-
使用对象
-
volatile
-
读
- volatile读
- LoadLoad
- LoadStore
-
写
- StoreStore
- volatile写
- StoreLoad
-
-
synchronized
-
进入管程
- monitor enter 等价于 volatile读
- LoadLoad(EnterLoad)
- LoadStore(EnterStore)
-
退出管程
- LoadStore(LoadExit)
- StoreStore(StoreExit)
- monitor exit 等价于 volatile写
- StoreLoad(ExitEnter)
-
-
CAS
- 同时具有volatile读和写的双内存语义
-
final
-
写
- 构造函数开始
- 对象的final域写\对象的final域的成员域写(Store)
- StoreStore(return之前)
- 构造函数return
- 将对象的引用赋值给引用变量obj(Store)
-
读
- 初次读引用变量obj(Load)
- LoadLoad(读final域之前)
-
初次读引用变量obj指向对象的final域\初次读引用变量obj指向对象的final域的成员域(Load)
-
-
-
-
-
-
Java关键字内存模型语义
-
volatile
对volatile变量的读写操作,在语义上和synchronized临界区一致,即临界区内的操作具有原子性,临界区前的操作对临界区可见,临界区内的操作对临界区后可见。
-
volatile操作具有原子性,包括对于64位的double或long类型变量。
可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
volatile引用或数组,volatile只能保证引用和数组引用的可见性,对于引用指向的对象、对象的属性、数组的元素,均不能保证可见性。
- synchronized
pre code A...
sync{
临界区 B...
}
after code C...
顺序一致性,通过临界区前后插入蔽障,保证三个区域代码顺序执行,A happens before B,B happens before C...,但ABC区域内部允许重排序。
- final
-
concurrent包\AQS框架
-
通用模式
- 1.声明volatile共享变量
- 2.使用CAS的原子条件更新来实现线程之间的同步
- 3.配合以volatile的读/写和CAS所具有的volatile读/写的内存语义来实现线程之间的通信
-
实现示意图
-
java关键字功能、性能
-
Volatile
保证可见性
-
内存屏障,避免JVM优化重排序指令带来的并法问题
每次对volatile变量写之后,编译器会在之后插入一条汇编指令,将本CPU内存刷到共享内存,同时使其他CPU内存无效化;该操作要求对volatile写之前的指令全部已经完成,所以编译器不能重排到写之后;
不是线程安全的,可与CAS结合使用,实现线程安全
-
Synchronized
- 偏向锁--基于锁总是由同一线程获得,完全消除锁开销
- 轻量级锁--基于在没有多线程竞争的情况下,通过CAS自旋代替互斥
- 重量级锁--基于互斥锁,没抢到锁即阻塞
-
ReentrantLock
-
可让等待线程放弃等待,执行其它任务
调用 可中断获取 方法,如果获取失败,会进入队列,自旋获取锁并检查中断标志,当中断标志被置位TRUE,抛出 中断异常,并取消获取锁。
提供公平锁
一个锁可绑定多个条件变量
-
CPU缓存带来的问题
-
简单示意图
CPU Cache分成了三个级别: L1, L2, L3. 级别越小越接近CPU, 所以速度也更快, 同时也代表着容量越小. L1是最接近CPU的, 它容量最小, 例如32K, 速度最快,每个核上都有一个L1 Cache(准确地说每个核上有两个L1 Cache, 一个存数据 L1d Cache, 一个存指令 L1i Cache). L2 Cache 更大一些,例如256K, 速度要慢一些, 一般情况下每个核上都有一个独立的L2 Cache; L3 Cache是三级缓存中最大的一级,例如12MB,同时也是最慢的一级, 在同一个CPU插槽之间的核共享一个L3 Cache.
-
缓存行(cache line)
缓存存取粒度单位,一般64字节。
通过命令查看:
cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
64一个Java long型占8字节, 所以从一条缓存行上你可以获取到8个long型变量. 所以如果你访问一个long型数组, 当有一个long被加载到cache中, 你将无消耗地加载了另外7个. 所以你可以非常快地遍历数组.
perf工具抓取L1cache未命中数据:
$ perf stat -e L1-dcache-load-misses java L1CacheMiss
-
缓存一致性协议(MESI)
http://blog.csdn.net/opensure/article/details/46669337
简单总结:
同一个cache line可以被缓存在多个核,当核A修改该cache line时,会导致其他核的cache line无效(注:1),当其他核需要修改该cache line时,会强制核A将修改的cache line刷回内存,然后其他核重新读取,以此保证一个cache line只能同时被一个核修改。注:1
每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态。-
带来的问题:伪共享
多线程程序需要并发操作volatile变量A和B时,如果AB属于同一个cache line,由于MESI协议,导致同一时刻只能有一个线程操作A或B中的一个变量,影响多线程程序的并发性能。
解决:可在AB之间填充无效变量,使AB处于不同的cache line,解决冲突。
Lock前缀指令会引起处理器缓存回写到内存。Lock前缀指令导致在执行指令期间,声言处理器的 LOCK# 信号。在多处理器环境中,LOCK# 信号确保在声言该信号期间,处理器可以独占使用任何共享内存。(因为它会锁住总线,导致其他CPU不能访问总线,不能访问总线就意味着不能访问系统内存),但是在最近的处理器里,LOCK#信号一般不锁总线,而是锁缓存,毕竟锁总线开销比较大。在8.1.4章节有详细说明锁定操作对处理器缓存的影响,对于Intel486和Pentium处理器,在锁操作时,总是在总线上声言LOCK#信号。但在P6和最近的处理器中,如果访问的内存区域已经缓存在处理器内部,则不会声言LOCK#信号。相反地,它会锁定这块内存区域的缓存并回写到内存,并使用缓存一致性机制来确保修改的原子性,此操作被称为“缓存锁定”,缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据。
一个处理器的缓存回写到内存会导致其他处理器的缓存无效。IA-32处理器和Intel 64处理器使用MESI(修改,独占,共享,无效)控制协议去维护内部缓存和其他处理器缓存的一致性。在多核处理器系统中进行操作的时候,IA-32 和Intel 64处理器能嗅探其他处理器访问系统内存和它们的内部缓存。它们使用嗅探技术保证它的内部缓存,系统内存和其他处理器的缓存的数据在总线上保持一致。例如在Pentium和P6 family处理器中,如果通过嗅探一个处理器来检测其他处理器打算写内存地址,而这个地址当前处理共享状态,那么正在嗅探的处理器将无效它的缓存行,在下次访问相同内存地址时,强制执行缓存行填充。
-
锁的一种实现
-
Synchronized
- 可重入
Lock
public class Counter{
public class Lock{
private boolean isLocked = false;
public synchronized void lock()
throws InterruptedException{
while(isLocked){
wait();
}
isLocked = true;
}
public synchronized void unlock(){
isLocked = false;
notify();
}
}
-
ReentrantLock
-
非公平
public class Lock{
boolean isLocked = false;
Thread lockedBy = null;
int lockedCount = 0;
-
public synchronized void lock()
throws InterruptedException{
Thread callingThread =
Thread.currentThread();
while(isLocked && lockedBy != callingThread){
wait();
}
isLocked = true;
lockedCount++;
lockedBy = callingThread;
}
public synchronized void unlock(){
if(Thread.curentThread() ==
this.lockedBy){
lockedCount--;
if(lockedCount == 0){
isLocked = false;
notify();
}
}
}
...
}
- 公平
public class FairLock {
private boolean isLocked = false;
private Thread lockingThread = null;
private List<QueueObject> waitingThreads =
new ArrayList<QueueObject>();
public void lock() throws InterruptedException{
QueueObject queueObject = new QueueObject();
boolean isLockedForThisThread = true;
synchronized(this){
waitingThreads.add(queueObject);
}
while(isLockedForThisThread){
synchronized(this){
isLockedForThisThread =
isLocked || waitingThreads.get(0) != queueObject;
if(!isLockedForThisThread){
isLocked = true;
waitingThreads.remove(queueObject);//加到锁的线程移除唤醒对象
lockingThread = Thread.currentThread();
return;
}
}
try{
queueObject.doWait();
}catch(InterruptedException e){
synchronized(this) { waitingThreads.remove(queueObject); }
throw e;
}
}
}
public synchronized void unlock(){
if(this.lockingThread != Thread.currentThread()){
throw new IllegalMonitorStateException(
"Calling thread has not locked this lock");
}
isLocked = false;
lockingThread = null;
if(waitingThreads.size() > 0){
waitingThreads.get(0).doNotify();
}
}
}
public class QueueObject {
private boolean isNotified = false;
public synchronized void doWait() throws InterruptedException {
while(!isNotified){
this.wait();
}
this.isNotified = false;
}
public synchronized void doNotify() {
this.isNotified = true;
this.notify();
}
public boolean equals(Object o) {
return this == o;
}
}
-
ReadWriteLock
public class ReadWriteLock{
private int readers = 0;
private int writers = 0;
private int writeRequests = 0;
public synchronized void lockRead()
throws InterruptedException{
while(writers > 0 || writeRequests > 0){
wait();
}
readers++;
}
public synchronized void unlockRead(){
readers--;
notifyAll();
}
public synchronized void lockWrite()
throws InterruptedException{
writeRequests++;
while(readers > 0 || writers > 0){
wait();
}
writeRequests--;
writers++;
}
public synchronized void unlockWrite()
throws InterruptedException{
writers--;
notifyAll();
}
}
并发集合、线程池
-
线程池-ThreadPoolExecutor
-
配置
-
数量配置
-
根据任务性质
- cpu密集型--N+1
- IO密集型--2N
- 混合型--如果cpu消耗和IO相当,拆分为cpu密集型和IO密集型
-
-
任务队列配置
- 任务有优先级--采用PriorityBlockingQueue
-
-
-
并发集合
-
ConcurrentModificationException
Iterator内部维护一个expectedModCount,当直接通过集合进行修改时,集合的modCount会改变,但expectedModCount不会改变,当Iterator调用hasNext()和Next()时,会比较modCount==expectedModCount,如果不等,说明集合被外部修改,抛出异常
-
ConcurrentHashMap
-
分段锁
size,未避免成为热点域,每个锁维护锁范围内的bucket的计数,size()函数返回所有计数之和,因为size()函数不会同时加上所有的锁,返回的值不精确,isEmpty()同理。
-
弱一致迭代器
当迭代已经开始,集合元素被修改,若被删除,迭代到该元素时,next()不会返回该元素;若增加了元素,next()可能返回该元素,也可能不。
-
队列的同步问题
-
阻塞队列
- ArrayBlockingQueue
- LinkedBlockingQueue
- PriorityBlockingQueue
- DelayQueue
- SynchronousQueue
- LinkedTransferQueue
- LinkedBlockingDeque
-
零散知识点
常用命令
-
jps
显示当前所有java进程pid
-
jmap
主要用于打印指定Java进程(或核心文件、远程调试服务器)的共享对象内存映射或堆内存细节
-
jstack
查看线程信息
-
jstat
主要利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控
jhat
jinfo?
-
javap?
反编译
String
-
intern()
jdk6.0:
常量池存在于方法区,和堆完全隔离,常量池只能存放字符串,intern()方法将字符串放入常量池,并返回指向方法区常量池相应字符串的引用;若字符串已存在,返回已存在字符串的引用。
jdk7.0:
常量池存在于堆,常量池可存放指向堆内String Obj的引用,string_obj.intern()方法将指向string_obj的引用存入常量池,并返回指向string_obj的引用;若字符串已存在,返回已存在字符串的引用;检查常量池字符串是否存在时,池内引用指向的string_obj的值也要算作存在。String s = new String("s");//创建两个对象,一个堆内string_obj,一个常量池内字符串;
String s = new String("s1") + new String("s2");//创建五个对象,堆内string_obj_s("s1s2"),string_obj("s1"),string_obj("s2"),常量池字符串"s1","s2".
String s = "test";//该字符串直接插入常量池; -
substring()
-
JDK6
-
String Obj
- char value[]
- int offset
- int count
实现:以char[]为入参new String(),String Obj引用传入的char [];substring产生一个新的String Obj,引用源String的value,通过改变offset和count实现。
缺点:子串一直拿着父串的引用,导致父不能释放内存
解决:通过String sub = s.substring() + "";返回一个包含新value的串
-
-
JDK7
-
String Obj
- char value[]
解决JDK6问题:substring()直接返回一个新的数组
-
-
equals() & hashcode()
反射&代理&注解
-
反射
通过反射API,在运行状态获取类的所有属性和方法,构造对象,并访问属性和方法。
public static Class<?> forName(String className)
throws ClassNotFoundException
public Method[] getMethods()
throws SecurityException
public Object invoke(Object obj,
Object... args)
throws IllegalAccessException,
IllegalArgumentException,
InvocationTargetException -
动态代理
-
例子
功能:禁止使用List接口中的add方法。
public List getList(final List list) {
return (List) Proxy.newProxyInstance(DummyProxy.class.getClassLoader(), new Class[] { List.class },
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("add".equals(method.getName())) {
throw new UnsupportedOperationException();
}
else {
return method.invoke(list, args);
}
}
});
}
-
- 简单原理
JVM创建一个代理类:
public final class $Proxy0 extends Proxy implements List
代理类保存InvocationHandler对象:
public $Proxy(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
代理类实现接口:
public final List_Method()
{
super.h.invoke(this, m3, null);
// 实际上就是调用MyInvocationHandler的public Object invoke(Object proxy, Method method, Object[] args)方法
}
-
注解
-
注解定义
@Target(ElementType.METHOD) //可注解范围,方法
@Retention(RetentionPolicy.RUNTIME) //注解有效范围,运行时有效
public @interface MyAnnotation
{
String myKey() default "hello"; //定义一个注解属性,类型为String,默认值为“hello”
} -
注解实施
@MyAnnotation(myKey="nihao")
public void method()
{
...
} -
注解处理器
注解本身不会对代码造成影响,需要注解处理器解析注解,实现注解功能。
使用反射API:
使用了注解的Class->Class中使用了注解的方法和属性->方法和属性的注解->注解值->实现自定义注解功能;public class myAnnotationHandler
{
public static void processAnnotations(Object obj)
{
Class<?> class = obj.getClass();
for (Method m : class.getDeclaredMethods() )
{
MyAnnotation myAnnotation = m.getAnnotation(MyAnnotation.class);
if (myAnnotation != null)
{
String annotationKey = myAnnotation.myKey();
...
//实现自定义注解功能
}
}
}
}
-
序列化
1、在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。
2、通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化
3、虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID)
4、序列化并不保存静态变量。
5、要想将父类对象也序列化,就需要让父类也实现Serializable 接口。
6、Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
7、服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。
- 用途:对象持久化(下盘及网络传输)