文档库 最新最全的文档下载
当前位置:文档库 › java问题定位技术

java问题定位技术

JAVA core dump分析
https://www.wendangku.net/doc/977810878.html,/site/html/64/t-4464.html

Thinking in Enterprise Java(企业级java编程思想)



java BaseTest > log.txt 2>&1
输入输出重定向

标准输入 stdin 0
标准输出 stdout 1
错误输出 stderr 2

pgrep java
kill -3
会在 log.txt 中输出 线程堆栈

80-20原则: 20%的代码消耗了80%的资源 20%的代码执行消耗了80%的时间


GC日志

-verbose:gc 或 -Xloggc:filename

java -Xloggc:gc.log BaseTest >log2.txt 2>&1

jvm gc 策略 参数
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction
JVM远程调试
-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=%DEBUG_PORT%,suspend=n
suspend 设为n时JVM会在打开调试端口后正常启动 若设为y则JVM启动后会等候调试器连接后才继续启动
JDWP Java Debug Wire Protocol Transport Interface
-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8090,suspend=n
检查调试端口是否打开
netstat -an|grep 8090
tcp 0 0 10.16.40.9:8090 10.16.40.11:4614 ESTABLISHED

查看使用8090的进程
lsof -i:8090
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
java 3747 dugang 6u IPv4 2835518 TCP dugang-desktop.local:8090->10.16.40.11:4614 (ESTABLISHED)

ps -ef|grep java
dugang 2072 1 1 05:43 ? 00:00:19 gedit /home/dugang/work/java/start
dugang 3747 3746 42 06:06 pts/2 00:02:55 java -Xloggc:gc.log -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8090,suspend=n BaseTest
dugang 4147 26995 0 06:13 pts/1 00:00:00 grep java

利用/proc 虚拟文件系统 监控进程
结合lsof进行分析 list open files

top -p 3747
pmap 3747
strace


第1章 java线程堆栈分析


cpu 内存占用过高分析
进程 线程 java线程 本地线程
进程分析 线程分析

HashMap 多线程读写 死循环

top -p 查看进程情况
top H -p 查看线程情况

Java Thread Dump
LWPID light weight process 线程 thread 轻量级线程

jni调用 GC频繁 导致 CPU过高

kill-3 打印线程堆栈

JProfiler等工具 在生产环境下慎用

资源等待 lock wait/notify

http-8082-Processor84" daemon prio=10 tid=0x0887c000 nid=0x5663 in Object.wait()
https://www.wendangku.net/doc/977810878.html,ng.Thread.State: WAITING (on object monitor)
at https://www.wendangku.net/doc/977810878.html,ng.Object.wait(Object.java:485)
at https://www.wendangku.net/doc/977810878.html,mons.pool.impl.GenericObjectPool.borrowObject(Unknown Source)

数据库连接池
资源太少 连接池配置得太少
资源占用时间过长 把复杂的业务处理代码放在数据库事务里
SQL语句不合理 没有使用索引
资源泄露 使用玩没有正常关闭


线程没有正常退出导致系统挂死

定时打印堆栈 跟踪排查长时间运行的线程

死循环
wait后没有接收到notify通知
与外部通讯(远程调用) 超时

锁等待 锁持有 死锁
"Thread-1021" prio=5 tid=0x0164eac0 nid=0x41e waiting

for monitor entry[...]
waiting to lock <0xbef17078> (a [B) //等待锁0xbef17078
找出占用锁的线程
locked <0xbef17078> //占有锁0xbef17078

线程死锁挂起分析

线程堆栈分析解决不了的问题
线程跑飞?
数据错误
数据库锁表(事务没有正常提交回滚)

在堆栈中能够表现出问题的 使用线程堆栈进行定位
无法在线程中留下痕迹的问题定位 需要依赖于一个好的日志设计
隐蔽诡异的问题借助经验 灵感

第2章 通过Java线程堆栈进行性能瓶颈分析

高性能 响应时间快 用户体验
高吞吐量 短信等 看处理能力 对每一个消息的处理时间不敏感
高性能+高吞吐量 行情系统 对吞吐量和处理时间均有很高的要求

压榨cpu

系统负载 响应时间 稳定性 可以拿快递举个例子

常见的性能瓶颈问题

同步方法滥用 synchronized lock
同步代码块 尽量少 处理时间尽量快 降低持锁时间
将耗时操作拿到同步块之外
CPU密集型 磁盘/网络IO密集型
缩小同步范围 只将访问共享资源的代码放在同步块中 确保 快进快出

sleep的滥用 sleep仍占有锁

字符串+ 滥用 (建议使用 StringBuffer StringBuilder)
日志滥用 日志级别设置不合理 log.isDebug(){...}
不恰当的线程模型 如在网络应用程序中 使用异步IO 非阻塞io

低效的sql 不恰当的数据库设计

GC参数设置不合理

内存泄露导致频繁GC

线程池线程数量配置太少

找出性能瓶颈 (木桶理论 最短板 道路通行能力决定于最窄处)
性能瓶颈是动态的 高负载下才可能成为瓶颈 性能测试 压力测试

出现性能瓶颈 3种典型的堆栈特征
1 绝大多数线程的堆栈都表现为在同一个调用上下文上 可能的原因
线程数量过少
锁粒度过大 竞争激烈
资源竞争厉害(数据库连接等)
大量耗时操作(磁盘IO等)
远程通信的对方处理能力低下

2 绝大多数线程处于等待状态 只有几个工作的线程 总体性能上不去 系统关键路径
如在消息分发系统 消息分发一般是一个线程 而消息处理是多个线程 这时候消息分发是瓶颈的话
在该关键路径上没有足够的能力给下个阶段输送大量的任务 导致其它地方空闲

3 线程总的数量很少。导致性能瓶颈的原因与上面的类似。这里线程很少,是由于某些线
程池实现使用另一种设计思路,当任务来了之后才new出线程来,这种实现方式下,线程
的数量上不去,就意味有在某处关键路径上没有足够的能力给下个阶段输送大量的任务,
从而不需要更多的线程来处理

线程池的设计


提高性能的方法

锁分离 隔离冲突域 ConcrrentHashMap 默认16个锁 每个锁守护一个Bucket
缺点 对整个容器独占访问更加昂贵 扩容时需要

对整个容器加锁 可以考虑增加面向整个容器的锁

对于像ConcurrentHashMap的实现 size是一个全局性的指标 如果把size实现为全局的
那么每一个操作可能都会访问到它 并且是同步的 这样这个地方就可能形成了"热点 这样
可能会造成严重的性能瓶颈 为了解决这种情况 ConcurrrentHashMap通过枚举每一个条目获得size 而不是维护一个全局的计数

避免过早优化 过早优化是万恶之源

性能调优的终结条件

压力上升 cpu占用上升 趋向100% 说明系统的性能已经榨干 性能调优即告结束
算法足够优化
没有线程/资源的使用不当而导致的CPU利用不足

硬件升级 垂直扩展
水平扩展 集群


性能调优工具

JProfiler OptimizeIt 性能消耗厉害 不适合高负荷的生产系统
跟性能相关的JVM参数
操作系统资源统计工具 top ps pstree free df iostat vmstat proc文件系统

线程堆栈分析


第3章 Java内存泄漏分析和堆内存设置


Garbage Collection GC

new 对象 堆heap内存分配
释放对象的根本原则就是该对象不再被引用 根引用法
JVM可以自动垃圾回收 但它只能回收满足垃圾回收条件的对象
在某些时候 需要人为去保证回收条件的满足

GC算法要做的2件事 检测垃圾对象 回收垃圾对象使用的内存空间

根对象的集合 根对象和某个对象之间存在引用路径 该对象是活的
确保不需要的对象不被引用到


内存泄漏常见场景

全局容器(hashMap ArrayList 等)
像Runnable对象等被java虚拟机自身管理的对象 没有正确的释放渠道 不去run 永远不会消亡

java对象内存占用大小计算 32位

https://www.wendangku.net/doc/977810878.html,ng.Object 8
https://www.wendangku.net/doc/977810878.html,ng.Float 16
https://www.wendangku.net/doc/977810878.html,ng.Double 16
https://www.wendangku.net/doc/977810878.html,ng.Integer 16
https://www.wendangku.net/doc/977810878.html,ng.Long 16
java.math.BigInteger 56 (*) 一般式变化的
https://www.wendangku.net/doc/977810878.html,ng.BigDecimal 72 (*)
https://www.wendangku.net/doc/977810878.html,ng.String 2*(Length) + 38 ±2
empty java.util.Vector 80
object reference 4
Float array 4*(Length) + 14 ±2

root set
mark sweep compact (压缩内存碎片)

java内存模型

32位操作系统 进程地址空间理论可达 4G
windows默认情况下 2G分配给应用 剩余2G保留给内核使用
3GB开关 应用可获取3G
Java进程内存=Java堆内存+本地内存+Perm内存

Java堆内存可以通过命令行指定:初使堆内存大小: -Xms:[k|m|g]最大堆内存大小: -Xmx:[k|m|g]
Perm内存可以通过命令行指定:初使Perm内存大小: -XX:PermSize=[k|m|g]最大Perm内存大小: -XX:MaxPermSize=[k|m|g]

本地内存大小不需要设置

OOM(OutOfMemroy) 堆内存 本地内存 perm区都会溢出


应用程序缓存功能 缓存容量限制
大量的长期活动对象
堆内存泄漏
本地内存泄漏 本地内存泄漏导致的OOM,一般原因有如果几个可能:
如果系统中存在JNI调用,本地内存泄漏可能存

在于JNI代码中
JDK的Bug
操作系统的Bug

Java内存泄漏的症状

可用内存越来越少 FULL GC 频繁 cpu占用100% 系统停滞 假死
系统抛OutOfMemory异常
虚拟机core dump

8190.813: [GC 164675K->251016K(1277056K), 0.0117749 secs]
8190.825: [Full GC 251016K->164654K(1277056K), 0.8142190 secs]


GC日志解析

8194.169: [Full GC 251028K->164660K(1277760K), 0.8144430 secs]
垃圾回收的时间点(秒为单位)
垃圾回收的类型(普通GC或者完全GC)
垃圾回收之前 Java对象占用的内存大小
垃圾回收之后 Java对象占用的内存大小
当前堆内存总的大小
本次垃圾回收消耗的时间

截取系统稳定运行以后的GC信息
过滤出FULL GC的行。只有FULL GC的行才有分析价值

如果FGC后的内存持续增长 基本上就可以断定系统存在内存泄漏
如果FGC后内存增长到一个值之后 又能回落 总体上处于一个动态平衡 那么内存泄漏基本可以排除

JProFIler OptimizeIt JProbe JConsole
-Xrunhprof gcviewer

本地内存泄漏可能还会引发如下异常:https://www.wendangku.net/doc/977810878.html,ng.OutOfMemoryError: unable to cre-ate new native thread
Exception in thread "main" https://www.wendangku.net/doc/977810878.html,ng.OutOfMemoryError: unable to create new native thread
at https://www.wendangku.net/doc/977810878.html,ng.Thread.start0(Native Method)
at https://www.wendangku.net/doc/977810878.html,ng.Thread.start(Thread.java:574)
at TestThread.main(TestThread.java:34)

出现这个异常 不是由于堆内存泄漏造成的内存不足 往往是创建的线程过多或者堆内存设置过大
在Java中创建一个java线程的时候 同时也创建一个操作系统的线程
而实际工作的是这个操作系统的线程 操作系统创建的线程对象是在本地内存上创建的

原因如下
超出最大能创建的线程数量 -Xss设置线程堆栈大小
swap分区不足
过大的堆内存设置 导致本地内存不足

在64位的系统下 进程的地址空间大 基本上不用考虑最大内存的因素 只要系统有足够大的物理内存

Perm内存泄漏
https://www.wendangku.net/doc/977810878.html,ng.OutOfMemoryError: PermGen space
jsp 动态编译
类动态修改生成 查看加载的类 -verbase:class
反射
MyClass.class.getMethod("foo",null).invoke(null,null);
在反射过程 对于普通的方法 JVM会动态产生如下的类
sun.reflect.GeneratedMethodAccessorN(N是一个数字)
如果反射的是构造函数 那么JVM会动态产生
sun.reflect.GeneratedConstructorAccessorN(N是一个数字)

-noclassgc 禁止class的GC

[Loaded sun.reflect.GeneratedMethodAccessor551 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor566 from __JVM_DefineClass__]
[Loaded sun.reflect.GeneratedMethodAccessor567 from __JVM_DefineClass__]

[Unloading class sun.reflect.GeneratedMethodAccessor570]
[Unloading class sun.reflect.GeneratedMethodAccessor565]
[Unloading class sun.reflect.GeneratedMethodAccessor551]


1. jmap -histo > objhist.log, 可以打印出当前对象的个数和大小
2. 如果

系统已经OutOfMemory异常并停止工作 可以通过jmap -heap:format=b 取内存信息
3. 在启动期间增加-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=x 系统 OutOfMemory之后 就会将内存信息和堆信息收集下来
在IBM的JDK[34]下,通过设置如下环境变量 可以通过kill -3 打印堆转储(程转储(thread dump):
export IBM_HEAP_DUMP=true
export IBM_HEAPDUMP=true
export IBM_HEAPDUMP_OUTOFMEMORY=true
export IBM_JAVACORE_OUTOFMEMORY=true
export IBM_JAVA_HEAPDUMP_TEXT=true

然后通过Heap Analysis工具对堆输出文件进行分析


new 了runnable对象 如果不提交给线程执行 该对象尽管没被显式引用 但是仍然永远不能被回收
如果提交这个runnable对象给一个线程去执行 执行完后 虚拟机会自动回收该垃圾对象

runnable对象?thread?

堆内存的设置原则

堆内存过小 GC频繁 CPU过高 还会导致OOM
堆内存过大 32位会导致 本地内存不足

汇编语言的直接寻址
mov ax [直接地址]
[直接地址]是32位长的一个数字 它最大只能指向4G的位置。

2位环境上 限制如下
正常的windows 最大可以设置大约1.5G的堆内存
使用/3G启动的windows 最大可设置大约2.8G的堆内存
有大内存支持的Linux 最大可设置大约2.8G的堆内存

根据经验 32位下堆内存大小一般不要超过1.2G
CPU 操作系统 JVM只要有一个是32位的 那么就受到32位的限制

Perm内存设置也不要过大 Perm内存的使用量在一个系统中一般比较固定 设大了实际上是对内存的一种浪费

CAPS (Call Attempts Per Second)每秒建立呼叫数量


64位的环境下堆内存设置 64位机器 理论上可以支持的内存为2的64次方 即4G*4G
堆内存大小设置基本上依赖于物理内存

堆大小对性能的影响有
有这样一个案例 在8 个CPU 的AIX 机器 配4GB 内存200 CAPS不到就开始出问题配16GB 的时候能支持1000 CAPS

一般建议将-Xms和-Xmx设得一样大 这样在系统启动期间可以将整个内存给预留下来 避免内存增长过程巨大开销带来性能暂时受影响

堆内存大 垃圾对象非常多 每次对象扫描用的时间非常可观 常常需要几秒钟的时间才能完成
在极端的情况下 甚至需要几十秒 如果采用串行垃圾回收 系统在垃圾回收期间 应用程序不能运行
在实时场合 这个时间会造成很严重的问题 因此在系统内存设置很大的场合 采用并行/并发垃圾回收

在一个堆内存为16G的系统上 曾经遇到过一次完全垃圾回收消耗了五分钟!!!!!!

top 中看到的是整个jvm进程占用的内存大小
堆内存使用情况观察 java命令行中增加-verbose:gc 观察FULL GC日志

实时应用 GC停顿 无法预测控制

实时虚拟机

实时虚拟机引入了一种确定性垃圾收集机制 提供了一个用于执行关键型应用的J2EE运

行时
确定性垃圾收集确保程序执行期间的暂停时间非常短 而且挂起的请求会在定义的时间帧中得到处理 从而允许购建高性能和确定性的应用程序

动态的“确定性”垃圾收集优先级 该策略被优化以确保暂停时间非常短 并限制定义良好的时间帧(也称为 滑动窗口sliding window)
中的这些暂停的总次数

响应时间 vs 吞吐量


real time
硬实时 软实时

在一个硬实时系统中 必须遵守时间期限 否则计算结果就会无效
汽车嵌入式系统 如果要加速 而电子加速器的响应延迟了 那么就会得到无法预料的行为
如果刹车系统延迟了 就会导致可怕的后果

音频流是一个软实时系统的例子 如果一个数据包延迟或丢失了 音频的质量会降低 但是流依然有效

实时系统必须以最小的开销实现可预测性

实时不意味着"快" 它意味着对一个实时事件的响应是可靠的 可预测的
实时计算机意味着在你给予的时限之内进行响应 大量的应用领域不能忍受哪怕是一秒的延迟 如飞机控制软件 核电厂软件等等
实时系统并不都是速度上的要求 尽管实时系统设计者尽量使系统更快
很明显 标准的java虚拟机不能满足实时的要求
在java的license中已经明确说明了这一点 java不能用在核电系统 也不能用在军事防御系统等等


第4章 并发和多线程


CPU密集型应用 多CPU 多线程才会提升性能 只有一个CPU 单线程不会带来性能的提升
忧郁线程切换等开销 反而会降低性能

当应用程序必须等待缓慢的资源(磁盘或网络IO)时 多线程会让系统的CPU充分利用起来 当一个线程被阻塞时 另一个线程可以继续利用CPU
使用多线程不会增加CPU 的处理能力 但在某些场景下可以更加充分地利用CPU
同一进程的多个线程共享同一片存储空间 保证多线程访问的数据一致性等问题

Java api与多线程编程相关的三大关键字 synchronized wait notify

保护的一定是变量 而不是代码 保护变量是通过代码段上加锁来实现的 而占有锁的则是线程

HashMap被多线程访问下 可能导致put或者get方法block(或者死循环)

加锁是为了保证数据的一致性 但同时可能引入了死锁的问题
死锁是一个经典的多线程问题 因为不同的线程都在等待那些根本不可能被释放的锁 从而导致所有的工作都无法完成

如何避免死锁

让所有的线程按照同样的顺序获得一组锁 这种方法消除了X 和Y 的拥有者分别等待被对方占有的锁
将多个锁组成一组并放到同一个锁下 只有一个锁 就不会存在死锁的问题

同步方法(静态方法和实例方法)
同步代码块 synchronized(lockObject) synchronized(this)

多线程编程中易犯的错误

锁范围过大
多把锁

使用造成死锁
多个共享变量共用一把锁 特别是在方法级别上使用synchronized关键字 人为造成的锁竞争
无意识地启动线程过多 超过最大限制 如在某个时刻 一个任务一个线程 如果任务成千上万 需要同时启动成千上万个线程 那么就会有大量失败
线程池的使用

调用线程的interrupted()方法硬性去停止线程 而不是通过run()的正常退出(return)来结束线程 增加状态变量 如 shutdown等

原子操作 同步保护 cpu指令集 32位64位差异

多线程在大多数场合可以提高整个系统的性能或者吞吐量
但一个系统中到底多少个线程才是合理的 总的来说 线程数量过大过少都不好 过大导致线程切换开销过大,
反而导致整个系统性能下降 过小导致CPU不能充分被利用 性能仍然上不去 系统到底使用少线程
依据系统线程运行是否充分利用了CPU. 如果每个线程都100%的使用CPU的话 那么系统一个线程就够了
但实际情况是 在如下情况下是不消耗CPU的 磁盘IO 网络IO 带有3D加速卡的图形运算 等待输入

线程池设计两种思路
线程池初始化时即将必要数量的线程创建出来 并一直存在 直到整个系统shutdown 工作线程数固定
线程池在初始化时 即创建很少数量的线程 当系统压力上去时 并导致当前线程数量不足时
那么会创建新的线程 一旦系统压力降下去 那么部分线程将被销毁 这种线程池的线程数量是在动态变化的


如果系统有可能在某个时刻任务过多,那么使用线程池要特别小心,因为这些任务可能
将线程池中的所有线程耗光, 同时将任务队列塞满, 从而造成任务提交失败, 因此将一个任务
提交给线程池的时候, 已经要对提交是否成功进行检查, 如果提交不成功, 就需要进行日志纪
录,以方便定位问题,否则这种问题非常难以定位。


notify和wait的组合 线程间通信

调用notify() 方法导致解除阻塞的线程是从因调用该对象的wait() 方法而阻塞的线程中随机选取的 无法预料哪一个线程将会被选择
调用notifyAll()方法将把因调用该对象的wait() 方法而阻塞的所有线程一次性全部解除阻塞 当然 只有获得锁的那一个线程才能进入可执行状态 其它继续回到等待状态

synchronized(obj) {
while(!condition)
obj.wait();
}
obj.doSomething();
}


synchronized(obj) {
condition = true;
obj.notify();
// ... 其他代码
}

其他代码执行完成 释放锁后 才会被唤醒

1. 调用obj的wait() notify()方法前 必须获得锁 即wait()方法必须写在synchronized(obj)代码段内
2. 调用obj.wait()后 线程A就释放锁 否则线程B无法获得obj锁 也就无法在synchronized(obj) 代码段内唤醒A
3. 当obj.wait()方法返回后 线程A将再次获得锁 才能继

续执行
4. 如果A1,A2,A3都在obj.wait() 则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)
5. obj.notifyAll()则能全部唤醒A1,A2,A3 但是要继续执行obj.wait()的下一条语句 必须获
得锁 因此 A1,A2,A3只有一个有机会获得锁继续执行 例如A1 其余的需要等待A1释放obj锁之后才能继续执行
6. 当B调用obj.notify/notifyAll的时候 此时B正持有obj锁 因此 A1,A2,A3虽被唤醒 但是仍无法获得锁
直到B退出synchronized块 释放锁后 A1,A2,A3中的一个才有机会获得锁继续执行


private static Object lock = new Object();

public static void main(String[] args) throws Exception {
System.out.println("before doWait(time)," + System.currentTimeMillis());
// 等待指定时间后 自动醒来 不需要notify
doWait(1000);
System.out.println("after doWait(time)," + System.currentTimeMillis());
System.out.println("before doWait()," + System.currentTimeMillis());
// 无限期等待 需要notify 唤醒
doWait();
System.out.println("after doWait()," + System.currentTimeMillis());
}
private static void doWait() throws Exception {
synchronized (lock) {
lock.wait();
}
}
private static void doWait(long time) throws Exception {
synchronized (lock) {
lock.wait(time);
}
}


线程的阻塞

共享资源访问控制 同步机制

阻塞机制 暂停一个线程 等待某个条件发生

sleep(long time) 休眠一定的时间 仍持有锁
suspend() 和resume() 方法 两个方法配套使用 不推荐使用
yield() 放弃当前CPU时间


wait()和notify()方法 两个方法配套使用 wait()使得线程进入阻塞状态 它有两种形式
一种允许指定以毫秒为单位的一段时间作为参数 另一种没有参数 前者当对应的notify()
被调用或者超出指定时间时线程重新进入可执行状态 后者则必须对应的notify () 被调用

wait notify 是Object方法 执行需要获得锁 执行完后会释放锁
前面其他方法属于Thread的方法 不会释放锁

wait notify 配合 synchronized 使用 只有synchronized 内才能获取锁 释放锁

IllegalMonitorStateException

synchronized方法或块提供了类似于操作系统原语的功能 它们的执行不会受到多线程机制的干扰
而wait notify这一对方法则相当于block 和wakeup 原语(这一对方法均声明为synchronized)

进程间通信的算法 如信号量算法





第5章 幽灵代码


try{...}catch(Throwable t){...}finally{...}
资源释放 文件 socket 关闭 数据库连接关闭
线程池 线程 内存 锁释放

在关键场合需要catch(Throwable) 而不是catch(Exception)
捕获Exception并不等于捕获了所有的异常

在C++中也支持异常这种概念 但并不是每个人都喜欢这种错误处

理模式 当然在C++中不使用异常机制是基于效率的考虑
但还有一个很重要的原因是这种不可预知的代码返回点导致的系统问题

返回错误码

异常退出导致的资源泄露

1 Context ctx = new InitialContext();
2 DataSource ds = (DataSource)ctx.lookup(dataSource
3 Connection con = ds.getConnection();
4 ... ...
5 conn.close(); //将连接回池(即资源回收)

1 FileInputStream infile = FileInputStream("test.txt");
2 int n = infile.read(buff); //从文件读取数据
3 infile.close(); //关闭文件


Thread.interrupted(); 方法详解

每个线程都有一个与之相关联的boolean 属性 用于表示线程的中断状态(interrupted status)
中断状态初始时为 false 当另一个线程通过调用 Thread.interrupt() 中断一个线程时 会出现以下两种情况
如果那个线程在执行一个低级可中断阻塞方法 例如 Thread.sleep() Thread.join() Object.wait() 那么它将取消阻塞并抛出 InterruptedException
否则 interrupt() 只是设置线程的中断状态 在被中断线程中运行的代码以后可以轮询中断状态 看看它是否被请求停止正在做的事情
中断状态可以通过 Thread.isInterrupted() 来读取 并且可以通过一个名为 Thread.interrupted() 的操作读取和清除


被阻塞的线程被中断 抛出InterruptedException 中断状态不变
Thread.sleep() Thread.join() Object.wait() 会抛出 InterruptedException
运行中的线程被中断 只修改中断状态 线程可以通过 Thread.interrupted() 作读取和清除该状态

并非所有的阻塞方法都抛出 InterruptedException
输入和输出流类会阻塞等待 I/O 完成 但是它们不抛出 InterruptedException

Java 理论与实践: 处理 InterruptedException
https://www.wendangku.net/doc/977810878.html,/developerworks/cn/java/j-jtp05236.html




Double-Checked Locking单例模式

1 public MyInstance getInstance()
2 {
3 if(this.instance == null){
4 synchronized(this){
5 this.instance = new MyInstance();
6 }
7 }
8 return this.instance;
9 }
将this.instance == null 的检查放在同步块中
1 public synchronized MyInstance getInstance()
2 {
3 if(this.instance == null){
4 this.instance = new MyInstance();
5 }
6 return this.instance;
7 }


第6章 常见的Java泥潭


不稳定的Runtime.getRuntime().exec()

Timer的使用
每new一个Timer 就会产生一个线 如果创建了过多的Timer就会导致线程耗尽。


池设计

对象池 线程池 连接池

池太小 没有效率 太大 加大GC压力



GC 三个阶段
mark阶段 扫描对象 将垃圾对象标识出来 这个阶段最耗时
sweep阶段 回收垃圾对象
compact阶段 将内存碎片重整连成片 以避免大对象分配失败

使用池 必定会引入同步机制 这个开销更大
只有在创建对象耗时时 使用池才有意义 譬如数据库连接池

构造函数或初始化函数初始

化创建对象
clone方式创建对象

线程池的作用
控制线程的最大数量
避免频繁的new Thread() 并销毁的操作 java线程与本地线程一一对应


数据库连接池

失效连接检测 清理
失效的原因

长时间无请求 数据库主动关闭连接
数据库重启 连接失效
网络闪断 导致socket失效

MySQL连接如果8小时未使用 再使用该连接进行数据库操作 会抛出如下异常
https://www.wendangku.net/doc/977810878.html,municationsException:Communications link failuredue to underlying exception

代理模式 close连接 > 归还连接











第7章 JVM p144

JVM命令行参数分为三种
标准的运行期参数
-X扩展参数
-XX扩展参数

-server
-client
-D= set a system property
-verbose[:class|gc|jni]
-version: require the specified version to run
-showversion print product version and continue
-X 打印X参数
-cp -classpath


内存比较大 对性能要求苛刻的场合 建议运行在server模式下
server模式启动过程比client慢 但运行期性能却比client模式高

J2SE 5.0中 只有两个CPU以上 2G内存以上的物理机器才会被看做是server类型的机器
不同的操作系统下面 server类型的标准也有不同 详细请参考SUN的官方文
尽量手工在命令行中指定虚拟机的运行模式


-verbose:class Java虚拟机运行期间 打印将class的加载情况
当系统中存在多个版本的jar包是时 这个信息特别有用

检查Permsize的内存溢出原因
在某些动态修改产生类的情况 不恰当的编程会出现不断 有类被加载而又不卸载的情况
最后造成perm内存耗尽 通过-verbose:gc可以检查出这类问题

-verbose:gc 输出GC日志

-verbose:jni 打印详细的JNI本地接口的使用情况
-version: 以指定版本的虚拟机来运行java程序
java -version:1.4 -classpath classes;lib/mylib.jar MyClass




第8章 关于字符集与编码

宽字节(wide char)与双字节(double byte char)
GBK与GB2312是什么关系
unicode在计算机中占几个字符
字符集 字符的集合 这个集合定义了每个字符的编码

常见的字符集有如下

ISO-8859-1 ISO-8859-16属于西欧语系
GB2312,共收录6763个简体汉字
GBK
BIG5,台湾香港地区使用的繁体字
GB18030 在GBK基础上增加了CJK统一汉字扩充
Unicode 试图囊括地球上所有的文字和符号,如:西欧文字,阿拉伯文字,中日韩等

字符集只定义了每个符号对应的编号 这个编号与计算机没有任何关系
字符集不规定每个字符在计算机中用几个字节表示 这个属于编码encoding的范畴

GBK编码是中国大陆制订的 等同于UCS的新的中文编码扩展国家标准
该编码标准兼容GB2312 共收录汉字21003个 符号883个 并提供1894个造字码位 简、繁体字融于一库


编码是指一个字符在计算

机中怎样存放 是采用一个字节 还是采用两个字节 还是采用不定长的字节

单字节
多字节(Multiple Byte Character Set,MBCS)
双字节 Double Byte Character Set,DBCS


GB2312,GBK,Big5一般既是字符集,又是编码。而unicode字符集是字符集,编码方式(即
在计算机内表示)则有很多种

最初采用两字节编码(ascii也采用两字节)
UTF-32采用固定的4字节编码

UTF-16和UCS-2相似,带有扩展机制,也是变长编
UTF-8采用变长编码(1-6字节不等)
GB18030实际上是另一种Unicode的编码方式
UTF-8优缺点
优点:
1. 对英文而言, 仍然采用单字节编码, 这样保证了后向的兼容性, 对英文文档不存在转
换的问题,原有的程序也没问题,同时比较节省空间
2. 编码中不会出现0x00,这样在C/C++这种以0x00作为字符串结束的语言下,不会导
致混乱
3. 容易找到字符的边界,不容易出现字被截断的情况
4. 逐字节编码,不存在大头/小头的问题(Big endian/Little endian)
缺点:
1. 判断字符数等需要从头开始遍历, 效率较低, 所以一般程序内部采用UCS-2定长字节
表示(定长字符, 字符串长度等运算非常容易) , 而保存在磁盘中则以UTF-8, 这样可
以保证空间的节省。

Unicode和UTF-8的关系

Unicode是字符集,而UTF-8则是Unicode字符集对应的计算机的存储格式

编码的识别
windowsxp记事本 输入 联通 保存打开后看不到 联通

在windows xp下的记事本文件,在文件头部纪录了编码的类型,编码是FFFE,表示该文件
是UTF-8的编码,而"联通"的ascii编码恰好是FFFE, 因此使得记事本程序误认为这是一个文件头。

http 使用Content-Type(HTTP header中或者meta tag)中进行表示
e-mail 使用Content-Type,Content-Transfer-Encoding进行表示

BOM BOM(byte-order mark)即字节顺序标记,它是插入到以UTF-8、UTF16或UTF-32编码Unicode文件开头的特殊标记,用来识别Unicode文件的编 码类型。

00 00 FE EF: UTF-32,big endian
FF FE 00 00: UTF-32,little endian
FE FF: UTF-16, big endian
FF FE: UTF-16, little endian
EF BB BF: UTF-8

xml解析器如何判断xml文件的编码

1. 先检查文件头是否有BOM编码,有,则根据BOM编码确定编码
2. 没有BOM编码,则按ascii格式读取xml声明,根据xml声明中的encoding属性确定编码,
如:
3. 如果二者都存在, 且BOM声明和xml声明的不一致, 或者声明的与文件中实际的数据不一
致,那么均不能正确完成读取

编码的转换

编码是可以转换的,但必须两个字符集都存在的字符才可以相互转换

iconv -f UTF-8 -t GB2312 file.txt > file-gb.txt,将utf转换成gb2312

UTF-8可以节省存储空间, 因此在文件的持久化方面, 采用得比较广泛
UTF-8每个

字符不定长度,因此在程序处理方面很不方便,如计算字符串的长度,搜索一个子串等都非常困难


在程序中一般采用的是固定字长的方式。因此多字节一般是指UTF-8编码方式,
而宽字节一般是unicode的编码方式(在计算机内存中可能采用四个字节)
一般的处理过程是这样的:
1. 从磁盘读入内存后,是UTF-8编码,即多字节方式。
2. c/c++程序通过调用mbstowcs()将多字节转换为宽字节, 即每个字符用unicode的四字节表示。(64位的机器可能是8字节)
具体4个字节或者8个字节对系统没有影响, 关键看wchat_t的长度。
3. 当需要写磁盘的时候,通过wcstombs()将宽字节转换为多字节,然后保存。
在C++中是不直接支持各种字符集的,需要手工编写代码进行转换,在不同的操作系统下函数名称亦有不同。
Linux下:
mbstowcs 多字节转换为宽字节,
wcstombs 宽字节转换为多字节
windows下:
MultiByteToWideChar() 多字节转换为宽字节
WideCharToMultiByte 宽字节转换为多字节

Java中,如果手工处理字符集的话,
String a = new String(bytes,"encodeing") 将bytes数组作为encoding的编码来处理
bytes=String.getBytes("encodeing") 将String中的字符串中按指定的编码方式转变为字节数组



第9章 常用的工具

JVM 远程调试
-Xdebug -Xrunjdwp:transport=dt.socket,server=y,address=3333,suspend=n
java自带工具
jconsole jstack 打印java线程堆栈
pgrep java
pstack 4078
jmap

jps (pgrep java 或 ps -ef|grep java )查看java进程ID
jmap 输出内存使用信息
jinfo 输出java 进程所有的配置信息 包括 java 系统属性和 jvm 命令行标记等
jstat 监视VM内存内的各种堆和非堆的大小及其内存使用量
jstack 输出 java 的线程堆栈信息 (与kill -3 得到的内容一致)
jstack -l 12806

jstat -class pid
jstat -gcutil pid

jmap -dump:format=b,file=heap.bin 12806
dump文件比较大



使用 jvisualvm 打开 heap.bin文件
实例数 占用的大小 top10
这个分析出来的内容和 jmap -histo 12806 给出统计信息差不多



jmap -histo 12806
jmap -heap 输出堆信息 及 相关jvm参数 GC方式
堆参数 堆使用情况 新生代(eden+from+to) 老生代 持久代
Mark Sweep Compact GC

Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 264241152 (252.0MB)
NewSize = 1048576 (1.0MB)
MaxNewSize = 4294901760 (4095.9375MB)
OldSize = 4194304 (4.0MB)
NewRatio = 2
SurvivorRatio = 8
PermSize = 12582912 (12.0MB)
MaxPermSize = 67108864 (64.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 54394880 (51.875MB)
used = 41803704 (39.86711883544922MB)
free = 12591176 (12.007881164550781MB)
76.85227727315512%

used
Eden Space:
capacity = 48365568 (46.125MB)
used = 41393968 (39.47636413574219MB)
free = 6971600 (6.6486358642578125MB)
85.58561330242209% used
From Space:
capacity = 6029312 (5.75MB)
used = 409736 (0.39075469970703125MB)
free = 5619576 (5.359245300292969MB)
6.795733907948369% used
To Space:
capacity = 6029312 (5.75MB)
used = 0 (0.0MB)
free = 6029312 (5.75MB)
0.0% used
tenured generation:
capacity = 120676352 (115.0859375MB)
used = 83568848 (79.69746398925781MB)
free = 37107504 (35.38847351074219MB)
69.25039298503157% used
Perm Generation:
capacity = 12582912 (12.0MB)
used = 51296 (0.048919677734375MB)
free = 12531616 (11.951080322265625MB)
0.4076639811197917% used

-permstat to print permanent generation statistics
-finalizerinfo

NewRatio = 2
new:old = 1:2



GC日志 GC FGC 次数 时间 频率 各部分内存占用
内存使用情况 新生代(eden from to) 老生代 持久代
jvm参数 (版本 jvm参数 GC策略 各代内存大小 newRation SurviorRatio)
线程堆栈(锁 状态 调用堆栈 运行时间 线程ID 本地线程ID)
jvm实例统计 实例数 占用的大小 top10

thread dump (windows 控制 ctrl+break, linux kill-3 ,jdk5+ jstack )
core dump

jvm线程 用户线程
不同时间点得thread dump信息对比分析

"Thread-1" prio=10 tid=0x08223860 nid=0xa waiting on condition [0xef47a000..0xef47ac38]
at https://www.wendangku.net/doc/977810878.html,ng.Thread.sleep(Native Method)
at testthread.MySleepingThread.method2(MySleepingThread.java:53)
- locked <0xef63d600> (a testthread.MySleepingThread)
at testthread.MySleepingThread.run(MySleepingThread.java:35)
at https://www.wendangku.net/doc/977810878.html,ng.Thread.run(Thread.java:595)
等到以下信息
线程的状态 waiting on condition
线程的调用栈
线程的当前锁住的资源 <0xef63d600>

线程的状态
Runnable
Wait on condition sleep 或 网络读写
Waiting for monitor entry 和 in Object.wait()
monitor 锁只能被一个线程拥有(Active Thread) 它线程都是 Waiting Thread
2个队列
Entry Set 相应的状态 Waiting for monitor entry
Wait Set 相应的状态 in Object.wait()

线程 何时进入 Wait Set
线程进入monitor 调用wait() 进入Wait Set
只有当别的线程在该对象上调用了 notify() 或者 notifyAll() Wait Set 队列中线程才得到机会去竞争

"Thread-1" prio=10 tid=0x08223250 nid=0xa in Object.wait() [0xef47a000..0xef47aa38]
at https://www.wendangku.net/doc/977810878.html,ng.Object.wait(Native Method)
- waiting on <0xef63beb8> (a java.util.ArrayList)
at https://www.wendangku.net/doc/977810878.html,ng.Object.wait(Object.java:474)
at testthread.MyWaitThread.run(MyWaitThread.java:40)
- locked <0xef63beb8> (a java.util.ArrayList)
at https://www.wendangku.net/doc/977810878.html,ng.Thread.run(Thread.java:595)

synchronized(obj) {
.........
obj.wait()

;
.........
}

线程执行时 先用 synchronized 获得了这个对象的 Monitor(对应于 locked <0xef63beb8> )
执行到 obj.wait() 线程放弃 Monitor的所有权 进入 wait set队列(对应于 waiting on <0xef63beb8> )

synchronized 和 monitor机制
Lock机制
Lock类只是一个普通类 JVM无从得知 Lock对象的占用情况 所以在线程 DUMP中 也不会包含关于 Lock的信息 关于死锁等问题 就不如用 synchronized的编程方式容易识别

死锁

热锁(活锁)
线程切换 临界区进出 锁竞争
频繁线程上下文切换 系统调用 导致 CPU在 系统态 运行

vmstat 可以查看 cup 系统占用和用户占用的时间

JAVA线程dump分析 https://www.wendangku.net/doc/977810878.html,/jzone/articles/303979.html

java 应用 cpu占用过高问题分析
top -H 查看CPU占用较多的线程 (pid为线程id)
jstack 获取 Java线程堆栈 根据 线程本地id nid 关联查找热点代码
代替以上人肉方法的一个辅助工具 https://www.wendangku.net/doc/977810878.html,/p/java-basic-diag-tool/wiki/UserGuide

org.vanadies.diagtool.jar org.vanadies.diagtool.ListBusyThreads

后台线程 Daemon线程 非后台线程运行完毕后 JVM就会退出
jvm 的后台进程 线程堆栈中有 daemon

"Attach Listener" daemon prio=10 tid=0x09fea000 nid=0x3255 waiting on condition [0x00000000]
https://www.wendangku.net/doc/977810878.html,ng.Thread.State: RUNNABLE


"Low Memory Detector" daemon prio=10 tid=0x0a013800 nid=0x320d runnable [0x00000000]
https://www.wendangku.net/doc/977810878.html,ng.Thread.State: RUNNABLE

"C1 CompilerThread0" daemon prio=10 tid=0x0a008000 nid=0x320c waiting on condition [0x00000000]
https://www.wendangku.net/doc/977810878.html,ng.Thread.State: RUNNABLE

"Signal Dispatcher" daemon prio=10 tid=0x0a006400 nid=0x320b runnable [0x00000000]
https://www.wendangku.net/doc/977810878.html,ng.Thread.State: RUNNABLE


"Finalizer" daemon prio=10 tid=0x0a001800 nid=0x320a in Object.wait() [0xb5043000]
https://www.wendangku.net/doc/977810878.html,ng.Thread.State: WAITING (on object monitor)
at https://www.wendangku.net/doc/977810878.html,ng.Object.wait(Native Method)
- waiting on <0x84cb0420> (a https://www.wendangku.net/doc/977810878.html,ng.ref.ReferenceQueue$Lock)
at https://www.wendangku.net/doc/977810878.html,ng.ref.ReferenceQueue.remove(ReferenceQueue.java:118)
- locked <0x84cb0420> (a https://www.wendangku.net/doc/977810878.html,ng.ref.ReferenceQueue$Lock)
at https://www.wendangku.net/doc/977810878.html,ng.ref.ReferenceQueue.remove(ReferenceQueue.java:134)
at https://www.wendangku.net/doc/977810878.html,ng.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)

"Reference Handler" daemon prio=10 tid=0x09ffcc00 nid=0x3209 in Object.wait() [0xb5094000]
https://www.wendangku.net/doc/977810878.html,ng.Thread.State: WAITING (on object monitor)
at https://www.wendangku.net/doc/977810878.html,ng.Object.wait(Native Method)
- waiting on <0x84cb0108> (a https://www.wendangku.net/doc/977810878.html,ng.ref.Reference$Lock)
at https://www.wendangku.net/doc/977810878.html,ng.Object.wait(Object.java:485)
at https://www.wendangku.net/doc/977810878.html,ng.ref.Reference$ReferenceHandler.run(Reference.java:116)
- locked <0x84cb0108> (a https://www.wendangku.net/doc/977810878.html,ng.ref.Reference$Lock)

#下面这2个非后台jvm线程
"VM Thread" prio=10 tid=0x09ff2400 nid=0x3208 runnable

"VM Periodic Task Thread" prio=10 tid=0x0a027000 nid=0x320e waiting on condition




unix下进程分析利器

pstack 打印当前进程的每个

线程的调用堆栈
pfiles 列出该进程打开的所有文件和socket linux下使用lsof
pldd 列出本进程使用的动态库。如果程序中有JNI调用,那么可以使用该命令找到该进程到底调用了哪些动态库
pmap
ptree 显示进程的父子关系 进程树 pstree
pwdx 显示指定进程的运行目录
plimit 显示指定进程的限制


top ps pstree lsof vmstat iostat sar free
strace tracert



第10章 Java最佳实践



消息队列设计

优先级不同的消息采用不同的队列
注意控制消息
关键处理线程确保永不会退出 catch(Throwable t){//....}



第11章 关于数据库

锁表与死锁

锁表 当一个事务在操作数据库(update等)的时候,在事务commit/rollback之前表的相关行
是会被一直锁住的,但如果事务在某些原因下一直没有被提交或回滚,那么相关的表是一直被锁住



第12章 工程实践



第13章 常见的案例

https://www.wendangku.net/doc/977810878.html,.SocketException: Too many open files
java.io.IOException:Too many open files
https://www.wendangku.net/doc/977810878.html,ng.StackOverflowError
https://www.wendangku.net/doc/977810878.html,.SocketException: Broken pipe 往已经关闭的管道或者socket写数据
HashMap的ConcurrentModi?cationException hashmap在迭代的同时 被其他线程修改
Iterator指向HashMap内部数据结构的一个元素, 当遍历下一个元素的时候, 还要借助于当前元素进行下一个遍历, 这
样如果有其它线程修改了这些元素,势必造成遍历混乱

HashMap是线程没有进行同步的,如果多个线程并行存取HashMap,只要有线程修改这个map,那么必须在外部对HashMap进行同步.
这里所说的修改是指添加或者删除元素等操作 如果仅仅是改变一个已有key的值不在这个范畴

添加或删除元素

数据混乱
未知的行为 如无限循环

在实际中 遇到的未加保护的HashMap访问导致的无限循环(死循环)最多。这种问题一旦发生,整个系统基本瘫痪

while(e != null)可能导致无限循环

Entry是一个链表结构, 通过遍历这个链表, 找到合适的元素,
如果这个链表的访问不加保护, 很容易造成一个首尾相接的闭环链表。一旦造成首尾相接, 那么无限循环的土壤也就生成了

HashMap infinite loop

HashMapInfiniteLoopTest

第14章 附录A JPROFILER内存泄漏精确定位


第15章 附录 B SUN JDK自带故障定位



















































相关文档