java虚拟机
- 本地方法区
- 方法区
- 栈区
- 堆区
- pc
jvm 有哪些内存空间
- 堆
- 永久区(产生大量的类,元数据保存在此)
- 线程栈
- 直接内存
- codecache
一些概念
- GC的对象是堆空间和永久区
内存模型
- 每一个线程有一个工作内存和内存独立
- 工作内存存放变量的拷贝
可见性
概念:一个线程修改了变量,其他线程可以立即知道
- volatile
- synchronized
- final
有序性
- 在本线程内,操作都是有序的
- 在线程外,操作是无序的
指令重排
- 一个线程内保证语义的串行性
- 编译器不考虑多线程之间的语义
常用的配置参数
Trace跟踪参数
-verbose:gc
-XX:+printGC
-XX:+printGCDetails //打印GC的详细信息
-XX:+printGCTimeStamps //打印GC发生的时间戳
-Xloggc:./log/gc.log //指定GC位置
-XX:+PrintHeapAtGC //在每一次GC之后,都打印堆的信息
-XX:+TraceClassLoading //跟踪每一个类的加载
-XX:+PrintClassHistogram //打印类的直方图,即使用情况
//堆的分配参数
-Xmx //最大堆
-Xms //最小堆
-Xmn //新生代大小
-XX:NewRatio //设置新生代:老年代的比 = 1:x
-XX:SurvivorRatio //两个Survivor:eden = 2:x
-XX:+HeapDumpOnOutOfMemoryError //OOM时导出堆文件
-XX:+HeapDumpPath //OOM时的路径
-XX:OnOutOfMemoryError //OOM时,执行指定的一个脚本
//永久区
-XX:PermSize //初始永久区
-XX:MaxPermSize //最大永久区
//栈大小分配
-Xss //决定函数调用深度,每个线程都有独立的栈空间,一般都是几百K
//例子
-Xmx20m -Xms20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=~/
垃圾回收的一些算法
引用计数法
- 根对象
引用计数法的问题
- 计数±法,影响性能
- 循环引用:根对象不可达对象(即垃圾对象)之间循环引用
标记-清除法
- 现代垃圾回收算法的思想基础
- 标记阶段
- 清除阶段
标记-压缩算法
- 不适合存活对象较多的场合,如老年代??
- 标记-清除算法上做了一些优化
- 标记阶段
- 复制压缩,清理边界外所有空间
复制算法
- 相比于标记-清除算法,较为高效
- 不适合于存活对象较多的场合,如老年代
- 将原有的内存空间分为两块,每次只使用其中的一块,在垃圾回收的时候,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收
- 缺点:每次只能使用一半的空间
复制算法扩展
- 老年代没有关系,作为担保空间
- 三块空间 1 + 2
- eden
- from
- to
- 大对象直接进入担保空间
- 老年对象进入老年代
- 剩余对象复制到
分代思想
- 少量对象存活,适合复制算法
- 大量对象存活,适合标记清理或者标记压缩
- 老年代
- 标记清除
- 标记压缩
- 新生代
- 复制算法
可触及性
可触及的
- 从根节点可以触及到这个对象
- finalize()方法只会被调用一次,避免使用该方法
可复活的
- 一旦所有引用被释放,就是可复活状态
- 因为在finalize()中可能复活该对象
不可触及的
- 在finalize()后,可能会进入不可触及状态
- 不可触及的对象不可能复活
- 可以回收
根
- 栈中引用的对象
- 方法区中静态成员或者常量引用的对象(全局对象)
- JNI方法栈中引用对象
Stop the world
- java中一种全局暂停的现象
- 全局停顿,所有java代码停止,native代码可以执行,但是不能和JVM交互
- 多半由于GC引起
- Dump线程
- 死锁检查
- 堆Dump
GC参数
堆细分
- eden
- from
- to
- tenured
串行收齐器
- 最老,最稳定
- 效率高
- 可能会产生较长的停顿
-XX:+UseSerialGC
- 新生代、老年代全部使用串行回收
- 新生代复制算法
- 老年代标记-压缩
并行收集器ParNew
-XX:+UseParNewGC
- 新生代并行
- 老年代串行
- Serial收齐器新生代的并行版本
- 复制算法
- 多线程,需要多核支持
-XX:ParallelGCThreads
限制线程数量- 多线程不一定快
Parallel收集器:新生代
- 类似ParNew
- 新生代复制算法
- 老年代标记压缩
- 更加关注吞吐量
-XX:+UseParallelGC
- 使用Parallel收集器 + 老年代串行
-XX:+UseParallelOldGC
- 使用Parallel收集器 + 老年代并行
并行回收器参数
-XX:MaxGCPauseMills
- 最大停顿时间,单位毫秒
- GC尽量保证回收时间不超过设定值
-XX:GCTimeRatio
- 0-100的取值范围
- 垃圾收集时间占总时间的比
- 默认99,即最大允许1%时间做GC
CMS收集器(老年代)
- Concurrent Mark Sweep 并发标记清除
- 标记清除算法
- 与标记-压缩相比
- 并发阶段会降低吞吐量
- 老年代收集器(新生代使用ParNew)
-XX:+UseConcMarkSweepGC
运行过程
-
初始标记(暂停)
- 根可以直接关联到的对象
- 速度快
-
并发标记(和用户线程一起)
- 主要标记过程,标记全部对象
-
重新标记(暂停)
- 由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正
-
并发清除(和用户线程一起)
- 基于标记结果,直接清理对象
特点
-
尽可能降低停顿
-
会影响系统整体吞吐量和性能
-
清理不彻底
-
因为和用户线程一起运行,不能在空间快满的时候再清理(在垃圾回收的时候,用户程序还在分配空间)
-XX:CMSInitiatingOccupancyFraction
设置触发GC- 如果内存预留不够,会引起concurrent mode failure
-
使用串行收集器作为后备
-
CMS参数
-XX:+UseCMSCompactAtFullCollection
Full GC 之后,进行一次整理,引起停顿-XX:+CMSFullGCsBeforeCompaction
设置几次Full GC后,进行一次碎片整理-XX:ParallelCMSThreads
设定CMS的线程数量
GC参数整理
-XX:+UseSerialGC //新生代,老年代串行收集器
-XX:SurvivorRatio //survivor:eden
-XX:NewRatio //新生代:老年代
-XX:+UseParNewGC //新生代并行收集
-XX:+UseParallelGC //新生代并行收集,吞吐量
-XX:+UseParallelOldGC //老年代并行收集
-XX:ParallelGCThreads //垃圾回收线程数
-XX:+UseConcMarkSweepGC //新生代并行,老年代CMS+串行收集
-XX:ParallelCMSThreads //CMS线程数
-XX:CMSInitiatingOccupancyFraction //
-XX:+UseCMSCompactAtFullCollection //垃圾回收后,是否进行一次内存压缩
-XX:CMSFullGCsBeforeCompaction //多少次CMS垃圾回收,进行一次内存压缩
-XX:+CMSClassUnloadingEnabled //允许对类元数据进行回收
-XX:CMSInitiatingPermOccupancyFraction //永久区占用率达到这一百分比时,启动CMS回收
-XX:UseCMSInitiatingOccupancyOnly //只有在到达阀值的时候,才进行CMS回收
性能监控工具
//系统工具,初步定位
uptime //Linux命令
pidstat -p xxxx -u 1 3 -t //-u显示CPU
pidstat -p xxx -d 1 3 -t //-d显示io
//java自带工具
jps //列出java进程
-l
-v
-m
jinfo //
jmap //堆信息
-histo xxx > xxx.log
-dump:format=b,file=filepath xxx //
jstack
-l //打印锁信息
-m //打印java和native的帧信息
-F //强制dump
jstack xxx >> filepath
JConsole
jvisualvm
堆分析
MAT是基于eclipse软件
偏向锁
- 大部分情况下是没有竞争的
- 偏心,锁会偏向于当前已经占有锁的线程
- 将对象头Mark的标记设置为偏向,并将线程ID写入对象头中
- 只要没有竞争,获得偏向锁的线程,在将来进入同步块,不需要做同步
- 当其他线程请求相同的锁时,偏向模式结束
XX:+UseBiasedLocking
,默认启动- 在竞争激烈的场合,偏向锁会增加系统负担
- 在jvm刚启动时,过几秒钟才会打开偏向锁。
-XX:BiasedLockingStartupDelay=0
刚启动即启用偏向锁
轻量级锁
- 嵌入在线程栈中
- 如果对象没有被锁定
- 将对象头的Mark指针保存到锁对象中
- 将对象头设置为指向锁的指针
- 轻量级锁失败,存在竞争,升级为重量级锁
- 竞争激烈,性能下降
自旋锁
- 如果线程可以很快获得锁,那么可以不在OS层挂起线程,让线程做几个空操作(自旋)
- JDK1.6 -XX:+UseSpinning 开启
- JDK1.7 中去掉此参数,改为内置实现
- 如果同步块很长,自旋失败,会降低系统性能
- 如果同步块很短,。。。。
总结
- 不是Java语言层面的锁优化方法
- 内置于JVM中的获取锁的优化方法,和获取锁的步骤
- 偏向锁可用,会先尝试偏向锁
- 轻量级锁可用,尝试轻量级锁
- 以上都失败,自旋锁
- 最后普通锁
锁优化
- 减小锁粒度
- 锁分离(读锁、写锁、LinkedBlockingQueue等等)
- 锁粗化,频繁进入锁
- 锁消除,JVM自动消除
-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
- 无锁
- CAS(Compare and Swap)
- 非阻塞同步
java.util.concurrent.atomic.AtomicInteger
Class文件
B //byte
C //char
D //double
F //float
I //int
J //long
S //short
Z //boolean
V //void
L //对象
[ //数组
[[ //二维数组
字节码执行过程
- 程序计数器
- 局部变量表
- 操作数栈
sipush xxx //将xxx压入堆栈当中
istore_1 //操作数栈 -> 局部变量表1的位置
iload_1 //局部变量表位置1 -> 操作数栈
iadd //结果入栈
idiv
ireturn //返回操作数栈顶元素
_nop //空指令
ldc //常量池中项入栈
pop //
dup //复制栈顶一个字长,入栈
JIT just in time
- 将一些热点代码编译成机器码
-XX:CompileThreshold=1000
-XX:+PrintCompilation
-Xint //解释执行
-Xcomp //全部编译执行
-Xmixed //默认,混合