jvm

第一章

整数的表达

原码:第一位为符号位(0为正数,1为负数)

5

00000101

负数反码:符号位不动,原码取反

正数反码:和原码相同

 

负数补码:符号位不动,反码加1

正数补码:和原码相同

-6

原码: 10000110

反码: 11111001

补码: 11111010

-1

原码: 10000001

反码: 11111110

补码: 11111111

 

打印整数的二进制(补码)表示

int a=-6;

for(int i=0;i<32;i++){

int t=(a & 0x80000000>>>i)>>>(31-i);

System.out.print(t);

}

 

为什么要用补码?

  1. 补码表示的0没有歧义(2)正数和负数使用补码做运算相当于用加法做运算

 

Float的表示与定义

支持 IEEE 754

s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm

指数:8  位数:23

 

e全0 尾数附加位为0  否则尾数附加位为1

s*m*2^(e-127)

 

-5

11000000101000000000000000000000

-1*2^(129-127)*(2^0+2^-2)

 

VM指令集

类型转化

l2i  

出栈入栈操作

aload  astore

运算

iadd  isub

流程控制

ifeq ifne

函数调用

invokevirtual invokeinterface  invokespecial  invokestatic

 

JVM需要对Java Library 提供以下支持:

反射 java.lang.reflect

ClassLoader

初始化class和interface

安全相关 java.security

多线程

弱引用

 

JVM的编译

源码到JVM指令的对应格式

Javap

JVM反汇编的格式

<index> <opcode> [ <operand1> [ <operand2>... ]] [<comment>]

 

第二章 JVM运行机制

JVM启动流程

JVM基本结构

PC寄存器(程序寄存器)

每个线程拥有一个PC寄存器

在线程创建时 创建

指向下一条指令的地址

执行本地方法时,PC的值为undefined

 

方法区

保存装载的类信息

类型的常量池

字段,方法信息

方法字节码

通常和永久区(Perm区)关联在一起

(JDK6时,String等常量信息置于方法;JDK7时,已经移动到了堆)

 

Java堆

和程序开发密切相关

应用系统对象都保存在Java堆中

所有线程共享Java堆

对分代GC来说,堆也是分代的

GC的主要工作区间

 

Java栈

线程私有

栈由一系列帧组成(因此Java栈也叫做帧栈)

帧保存一个方法的局部变量表、操作数栈、常量池指针

每一次方法调用创建一个帧,并压栈

 

Java栈 – 栈上分配

变量必选线程私有

线程调用结束后帧回收

Java栈 – 栈上分配

逃逸:当一个对象的指针被多个方法或线程引用时,我们称这个指针发生了逃逸,一般情况返回对象、对全局变量的赋值一般都会发生逃逸。

小对象(一般几十个bytes),在没有逃逸的情况下,可以直接分配在栈上

直接分配在栈上,可以自动回收,减轻GC压力

大对象或者逃逸对象无法栈上分配

 

打开逃逸分析

-server -Xmx10m -Xms10m

-XX:+DoEscapeAnalysis -XX:+PrintGC

 

内存模型

每一个线程有一个工作内存和主存独立

工作内存存放主存中变量的值的拷贝

当数据从主内存复制到工作存储时,必须出现两个动作:第一,由主内存执行的读(read)操作;第二,由工作内存执行的相应的load操作;当数据从工作内存拷贝到主内存时,也出现两个操作:第一个,由工作内存执行的存储(store)操作;第二,由主内存执行的相应的写(write)操作

每一个操作都是原子的,即执行期间不会被中断

对于普通变量,一个线程中更新的值,不能马上反应在其他变量中,如果需要在其他线程中立即可见,需要使用 volatile 关键字

可见性:一个线程修改了变量,其他线程可以立即知道

保证可见性的方法:volatile

synchronized (unlock之前,写变量值回主存)

final(一旦初始化完成,其他线程就可见)

 

有序性

在本线程内,操作都是有序的

在线程外观察,操作都是无序的。(指令重排 或 主内存同步延时)

指令重排

线程内串行语义

写后读 a = 1;b = a; 写一个变量之后,再读这个位置。

写后写 a = 1;a = 2; 写一个变量之后,再写这个变量。

读后写 a = b;b = 1; 读一个变量之后,再写这个变量。

以上语句不可重排

编译器不考虑多线程间的语义

--可重排: a=1;b=2;

--指令重排 – 破坏线程间的有序性

--指令重排 – 保证有序性的方法 synchronized

--指令重排的基本原则

--程序顺序原则:一个线程内保证语义的串行性

--volatile规则:volatile变量的写,先发生于读

--锁规则:解锁(unlock)必然发生在随后的加锁(lock)前

--传递性:A先于B,B先于C 那么A必然先于C

--线程的start方法先于它的每一个动作

--线程的所有操作先于线程的终结(Thread.join())

--线程的中断(interrupt())先于被中断线程的代码

--对象的构造函数执行结束先于finalize()方法

解释运行

--解释执行以解释方式运行字节码

--解释执行的意思是:读一句执行一句

编译运行(JIT)

--将字节码编译成机器码

--直接执行机器码

--运行时编译

--编译后性能有数量级的提升

 

 

第三章 JVM配置参数

 

Trace跟踪参数

-verbose:gc  -XX:+printGC  可以打印GC的简要信息

-XX:+PrintGCDetails 打印GC详细信息

-XX:+PrintGCTimeStamps 打印CG发生的时间戳

-Xloggc:log/gc.log指定GC log的位置,以文件输出

-XX:+PrintHeapAtGC 每次一次GC前后,都打印堆信息

-XX:+TraceClassLoading监控类的加载

-XX:+PrintClassHistogram按下Ctrl+Break后,打印类的信息

堆的分配参数

-Xmx –Xms指定最大堆和最小堆

-Xmn

设置新生代大小

-XX:NewRatio=4

新生代(eden+2*s)和老年代(不包含永久区)的比值

4 表示 新生代:老年代=1:4,即年轻代占堆的1/5

-XX:SurvivorRatio=8

设置两个Survivor区和eden的比

8表示 两个Survivor :eden=2:8,即一个Survivor占年轻代的1/10

XX:+HeapDumpOnOutOfMemoryError

OOM时导出堆到文件

-XX:+HeapDumpPath

导出OOM的路径

-Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/a.dump

XX:OnOutOfMemoryError

在OOM时,执行一个脚本

"-XX:OnOutOfMemoryError=D:/tools/jdk1.7_40/bin/printstack.bat %p“

 当程序OOM时,在D:/a.txt中将会生成线程的dump

可以在OOM时,发送邮件,甚至是重启程序

根据实际事情调整新生代和幸存代的大小

官方推荐新生代占堆的3/8

幸存代占新生代的1/10

在OOM时,记得Dump出堆,确保可以排查现场问题

永久区分配参数(几百兆)

-XX:PermSize  -XX:MaxPermSize

设置永久区的初始空间和最大空间

打开堆的Dump

堆空间实际占用非常少

但是永久区溢出 一样抛出OOM ,如果堆空间没有用完也抛出了OOM,有可能是永久区导的

-Xss

通常只有几百K

决定了函数调用的深度

每个线程都有独立的栈空间

局部变量、参数 分配在栈上

 

  • GC算法与种类

 

GC的概念:防止内存溢出,Java中,GC的对象是堆空间和永久区

 

GC算法

-引用计数法

引用计数法的问题

引用和去引用伴随加法和减法,影响性能

很难处理循环引用

 

-标记清除

标记-清除算法是现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。

 

-标记压缩

标记-压缩算法适合用于存活对象较多的场合,如老年代。它在标记-清除算法的基础上做了一些优化。和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记。但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。

-复制算法

与标记-清除算法相比,复制算法是一种相对高效的回收方法

不适用于存活对象较多的场合 如老年代

将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收

分代思想

依据对象的存活周期进行分类,短命对象归为新生代,长命对象归为老年代。

根据不同代的特点,选取合适的收集算法

少量对象存活,适合复制算法

大量对象存活,适合标记清理或者标记压缩

GC算法总结整理

引用计数

没有被Java采用

标记-清除(老年代)

标记-压缩(老年代)

复制算法(新生代)

 

可触及性

可触及的:从根节点可以触及到这个对象

可复活的:一旦所有引用被释放,就是可复活状态;因为在finalize()中可能复活该对象

不可触及的:在finalize()后,可能会进入不可触及状态;不可触及的对象不可能复活;可以回收(真正意义上可回收对象)

经验:避免使用finalize(),操作不慎可能导致错误。

优先级低,何时被调用(在GC时被调用), 不确定

何时发生GC不确定

可以使用try-catch-finally来替代它

栈中引用的对象

方法区中静态成员或者常量引用的对象(全局对象)

JNI方法栈中引用对象

 

Stop-The-World

Java中一种全局暂停的现象

全局停顿,所有Java代码停止,native代码可以执行,但不能和JVM交互

多半由于GC引起

Dump线程

死锁检查

堆Dump

 

  • GC参数

 

堆的回顾

 

串行收集器(Serial收集器)

最古老,最稳定

效率高

可能会产生较长的停顿

-XX:+UseSerialGC

新生代、老年代使用串行回收

新生代复制算法

老年代标记-压缩

 

并行收集器(ParNew收集器,Parallel收集器)

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

CMS运行过程比较复杂,着重实现了标记的过程,可分为

初始标记:根可以直接关联到的对象,速度快

并发标记(和用户线程一起):主要标记过程,标记全部对象

重新标记:由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正

并发清除(和用户线程一起):基于标记结果,直接清理对象

 

尽可能降低停顿

会影响系统整体吞吐量和性能

比如,在用户线程运行过程中,分一半CPU去做GC,系统性能在GC阶段,反应速度就下降一半

清理不彻底

因为在清理阶段,用户线程还在运行,会产生新的垃圾,无法清理

因为和用户线程一起运行,不能在空间快满时再清理

-XX:CMSInitiatingOccupancyFraction设置触发GC的阈值

如果不幸内存预留空间不够,就会引起concurrent mode failure

 

-XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次碎片整理

整理过程是独占的,会引起停顿时间变长

-XX:+CMSFullGCsBeforeCompaction

设置进行几次Full GC后,进行一次碎片整理

-XX:ParallelCMSThreads

设定CMS的线程数量

 

GC参数整理

-XX:+UseSerialGC:在新生代和老年代使用串行收集器

-XX:SurvivorRatio:设置eden区大小和survivior区大小的比例

-XX:NewRatio:新生代和老年代的比

-XX:+UseParNewGC:在新生代使用并行收集器

-XX:+UseParallelGC :新生代使用并行回收收集器

-XX:+UseParallelOldGC:老年代使用并行回收收集器

-XX:ParallelGCThreads:设置用于垃圾回收的线程数

-XX:+UseConcMarkSweepGC:新生代使用并行收集器(UseParallelGC),老年代使用CMS+串行收集器

-XX:ParallelCMSThreads:设定CMS的线程数量

-XX:CMSInitiatingOccupancyFraction:设置CMS收集器在老年代空间被使用多少后触发

-XX:+UseCMSCompactAtFullCollection:设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理

-XX:CMSFullGCsBeforeCompaction:设定进行多少次CMS垃圾回收后,进行一次内存压缩

-XX:+CMSClassUnloadingEnabled:允许对类元数据进行回收

-XX:CMSInitiatingPermOccupancyFraction:当永久区占用率达到这一百分比时,启动CMS回收

-XX:UseCMSInitiatingOccupancyOnly:表示只在到达阀值的时候,才进行CMS回收

 

 

  • 类装载器

 

class装载验证流程

加载:装载类的第一个阶段;取得类的二进制流;转为方法区数据结构;在Java堆中生成 对应的java.lang.Class对象

链接

--验证

目的:保证Class流的格式是正确的

文件格式的验证

是否以0xCAFEBABE开头

版本号是否合理

元数据验证

是否有父类

继承了final类?

非抽象类实现了所有的抽象方法

字节码验证 (很复杂)

运行检查

栈数据类型和操作码数据参数吻合

跳转指令指定到合理的位置

符号引用验证

常量池中描述类是否存在

访问的方法或字段是否存在且有足够的权限

 

--准备

分配内存,并为类设置初始值 (方法区中)

public static int v=1;

在准备阶段中,v会被设置为0

在初始化的<clinit>中才会被设置为1

对于static final类型,在准备阶段就会被赋上正确的值

public static final  int v=1;

--解析

符号引用替换为直接引用

初始化

执行类构造器<clinit>

static变量 赋值语句

Static{}语句

子类的<clinit>调用前保证父类的<clinit>被调用

<clinit>是线程安全的

 

什么是类装载器ClassLoader

ClassLoader是一个抽象类

ClassLoader的实例将读入Java字节码将类装载到JVM中

ClassLoader可以定制,满足不同的字节码流获取方式

ClassLoader负责类装载过程中的加载阶段

 

ClassLoader的重要方法

public Class<?> loadClass(String name) throws ClassNotFoundException

载入并返回一个Class

protected final Class<?> defineClass(byte[] b, int off, int len)

定义一个类,不公开调用

protected Class<?> findClass(String name) throws ClassNotFoundException

loadClass回调该方法,自定义ClassLoader的推荐做法

protected final Class<?> findLoadedClass(String name)

寻找已经加载的类

 

JDK中ClassLoader默认设计模式

BootStrap ClassLoader (启动ClassLoader)    rt.jar /-Xbootclasspath

 

Extension ClassLoader (扩展ClassLoader)     %JAVA_HOME%/lib/ext/*.jar

 

App ClassLoader (应用ClassLoader/系统ClassLoader)  Classpath下

 

Custom ClassLoader(自定义ClassLoader)

 

自底向上检查类是否已经加载

自顶向下尝试加载类

Thread. setContextClassLoader()

    上下文加载器

是一个角色

用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题

基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例

 

 

打破常规模式

热替换

 

  • 性能监控工具

 

uptime

系统时间

运行时间:例子中为7分钟

连接数:每一个终端算一个连接

1,5,15分钟内的系统平均负载:运行队列中的平均进程数

 

Top

 

vmstat

可以统计系统的CPU,内存,swap,io等情况

CPU占用率很高,上下文切换频繁,说明系统有线程正在频繁切换

 

 

Java自带的工具

Jps -m -l -v

列出java进程,类似于ps命令

参数-q可以指定jps只输出进程ID ,不输出类的短名称

参数-m可以用于输出传递给Java进程(主函数)的参数

参数-l可以用于输出主函数的完整路径

参数-v可以显示传递给JVM的参数

Jinfo

可以用来查看正在运行的Java应用程序的扩展参数,甚至支持在运行时,修改部分参数

-flag <name>:打印指定JVM的参数值

-flag [+|-]<name>:设置指定JVM参数的布尔值

-flag <name>=<value>:设置指定JVM参数的值

 

Jmap

生成Java应用程序的堆快照和对象的统计信息

jmap -histo 2972 >c:\s.txt

 

Dump堆

jmap -dump:format=b,file=c:\heap.hprof 2972

Jstack

    打印线程dump

-l 打印锁信息

-m 打印java和native的帧信息

-F 强制dump,当jstack没有响应时使用

JConsole

图形化监控工具

可以查看Java应用程序的运行概况,监控堆信息、永久区使用情况、类加载情况等

Visual VM

Visual VM是一个功能强大的多合一故障诊断和性能监控的可视化工具

 

  • java堆分析

 

内存溢出(OOM)的原因

MAT使用基础

浅堆(Shallow Heap)与深堆(Retained Heap)

显示入引用(incoming)和出引用(outgoing)

支配树

使用Visual VM分析堆

Tomcat OOM分析案例

 

 

线程安全

对象头Mark

Mark Word,对象头的标记,32位

描述对象的hash、锁信息,垃圾回收标记,年龄

指向锁记录的指针

指向monitor的指针

GC标记

偏向锁线程ID

 

 

偏向锁

大部分情况是没有竞争的,所以可以通过偏向来提高性能

所谓的偏向,就是偏心,即锁会偏向于当前已经占有锁的线程

将对象头Mark的标记设置为偏向,并将线程ID写入对象头Mark

只要没有竞争,获得偏向锁的线程,在将来进入同步块,不需要做同步

当其他线程请求相同的锁时,偏向模式结束

-XX:+UseBiasedLocking 默认启用

在竞争激烈的场合,偏向锁会增加系统负担

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

 

 

轻量级锁

BasicObjectLock 嵌入在线程栈中的对象

普通的锁处理性能不够理想,轻量级锁是一种快速的锁定方法。

如果对象没有被锁定

将对象头的Mark指针保存到锁对象中

将对象头设置为指向锁的指针(在线程栈空间中)

 

 

如果轻量级锁失败,表示存在竞争,升级为重量级锁(常规锁moniter)

在没有锁竞争的前提下,减少传统锁使用OS互斥量产生的性能损耗

在竞争激烈时,轻量级锁会多做很多额外操作,导致性能下降

 

自旋锁(存在竞争)

当竞争存在时,如果线程可以很快获得锁,那么可以不在OS层挂起线程,让线程做几个空操作(自旋)

JDK1.6中-XX:+UseSpinning开启

JDK1.7中,去掉此参数,改为内置实现

如果同步块很长,自旋失败,会降低系统性能

如果同步块很短,自旋成功,节省线程挂起切换时间,提升系统性能

 

偏向锁,轻量级锁,自旋锁总结

不是Java语言层面的锁优化方法

内置于JVM中的获取锁的优化方法和获取锁的步骤

偏向锁可用会先尝试偏向锁

轻量级锁可用会先尝试轻量级锁

以上都失败,尝试自旋锁

再失败,尝试普通锁,使用OS互斥量在操作系统层挂起

 

 

 

减少锁持有时间

减小锁粒度

将大对象,拆成小对象,大大增加并行度,降低锁竞争

偏向锁,轻量级锁成功率提高

ConcurrentHashMap

HashMap的同步实现

Collections.synchronizedMap(Map<K,V> m)

返回SynchronizedMap对象

锁分离

根据功能进行锁分离

ReadWriteLock

读多写少的情况,可以提高性能

 

锁粗化

锁消除

-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks

 

无锁

无锁是乐观的操作

无锁的一种实现方式

CAS(Compare And Swap)

非阻塞的同步

CAS(V,E,N)

在应用层面判断多线程的干扰,如果有干扰,则通知线程重试

 

  • Class文件结构

 

语言无关性

文件结构

魔数 0xCAFEBABE 表明是class

版本

常量池

访问符

类、超类、接口

字段

方法

属性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值