一、JVM概念
JVM:Java Virtual Machine的简称,即为java虚拟机。
虚拟机:指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。
常用的虚拟机有VMWare、Visual Box、JVM等。
VMWare、Visual Box是使用软件模拟物理的CPU指令集,JVM模拟字节码指令集。广泛使用的JVM是HotSpot。
JDK版本:JDK1.2开始 称为Java 2,J2SE J2EE J2ME 出现,JDK加入了Swing ,Collections等内容。若要在JVM运行,则需要满足JVM规范。
能在JVM运行的语言不仅有Java,还有Scala,Clojure,Groovy等等。但JVM中许多规范最初还是为Java设计的。
二、运行机制
JVM启动流程:见JVM附录图
JVM基本结构:见JVM附录图
PC寄存器:指向下一条指令的地址,在创建每个线程时都对应创建寄存器。执行本地方法时,其值为undefined。
方法区包含:
装载的类信息
类型的常量池(JDK7中String等常量信息移到堆中)
字段,方法信息
方法字节码
通常和永久区关联在一起
Java堆:所有的对象都存在堆中,被所有的线程共享。堆是分区的,分为eden(伊甸园区)、s0和s1(幸存区)、tenured(老年区)。
Java栈:
线程私有,栈由一系列帧组成。
每一次方法调用时会创建一个帧,并压栈,在方法调用结束时,回收帧。
帧保存一个方法的局部变量、操作数栈、常量池指针。
栈上分配:对于小对象,可以直接在栈上分配,减轻GC压力。但大对象不能在堆上分配。
栈、堆、方法区交互 :见JVM附录图
内存模型:每个线程都有一个线程内存和一个主内存。线程内存中存放的是主内存中值的拷贝 : 见JVM附录图
三、JVM配置参数
Trace跟踪参数:
-verbose:gc
-XX:+printGC 可以打印GC的简要信息
-XX:+PrintGCDetails 打印GC详细信息
-XX:+PrintGCTimeStamps 打印CG发生的时间戳
-Xloggc:log/gc.log 指定GC log的位置,以文件输出
-XX:+PrintHeapAtGC 每次一次GC后,都打印堆信息
-XX:+TraceClassLoading 监控类的加载
堆的分配参数:
-Xmx –Xms 指定最大堆和最小堆
-Xms(M > 1G ? 1G/64 : M/64) , -Xmx(M > 1G ? 1G/4 : M/4) 例如:-Xmx20m -Xms5m 最大堆为20m,最小堆为5m
-Xmn 设置新生代大小
-XX:NewRatio 老年代(不包含永久区)和 新生代(eden+2*s)的比值
例如:4 表示 老年代:新生代=4:1,即新生代占堆的1/5
-XX:SurvivorRatio 设置eden和两个Survivor区的比
8表示 eden:两个Survivor =8:2,即一个Survivor占新生代的1/10
(官方推荐新生代占堆的3/8,幸存代占新生代的1/10)
-XX:+HeapDumpOnOutOfMemoryError OOM时导出堆到文件
-XX:+HeapDumpPath 导出OOM的路径
-XX:PermSize -XX:MaxPermSize 设置永久区的初始空间和最大空间
永久区溢出会抛出OOM
-Xss:设置栈大小
通常只有几百K
决定了函数调用的深度(递归深度)
每个线程都有独立的栈空间
局部变量、参数 分配在栈上
四、GC算法与种类
GC:Garbage Collection 垃圾收集。Java中不需手动释放对象内存,通过垃圾回收机制自动实现。
Java中,GC的对象是堆空间和永久区。
GC算法:
(1)引用计数法:每个对象对应一个引用计数器。当其它对象引用该对象时,计数
器加1 ,引用失效(如将对象置为null ,obj = null)时减1,当计数器的值为0时,
表示这个对象是可回收的对象。
缺点:不能解决对象循环引用的情况,Java中不使用。
(2)标记清除法:分两个阶段,标记阶段和清除阶段;
从根节点出发,可到达的对象标记为可达对象。没有被标记的即表示为
未被引用的垃圾对象。在清除阶段,即可删除把未被标记的对象清除。
(3)标记压缩法:对标记清除法的优化。标记对象后,将存活的对象压缩到内存的一端。
(4)复制算法:将内存分为两块,每次只使用其中一块。垃圾回收时,将正在使用
的内存中的存活对象复制到未使用的内存中,然后把正在使用的内存中的所有对象
清除,最后交换两块内存的角色。(在其中一块查找存活对象时,可使用标记清除法)
少量对象存活,适合复制算法 ,比如幸存代
大量对象存活,适合标记清理或者标记压缩,比如老年代
对象存活周期进行分类,短命对象归为新生代,长命对象归为老年代
所有的算法,需要能够识别一个垃圾对象,才能进行垃圾回收。
可触及性:
(1)可触及的:从根节点可以触及到这个对象
(2)可复活的:一旦所有引用被释放,就是可复活状态,因为在finalize()中可能复活该对象
(3)不可触及的:在finalize()后,可能会进入不可触及状态(避免使用finalize() )
不可触及的对象不可能复活,可以回收
Stop-The-World
Java中一种全局暂停的现象
全局停顿,所有Java代码停止,native代码可以执行,但不能和JVM交互
多半由于GC引起
Dump线程
死锁检查
堆Dump
危害
长时间服务停止,没有响应
可能引起主备切换,严重危害生产环境。
GC垃圾回收器:
串行回收器:效率高,可能会产生较长的停顿 ,使用方法 -XX:+UseSerialGC
并行回收器:多线程执行,
使用方式:
-XX:+UseParNewGC(新生代并行,老年代串行)
-XX:+UseParallelGC (新生代、老年代并行)
-XX:+UseParallelOldGC (老年代并行、新生代串行)
CMS收集器:Concurrent Mark Sweep 并发标记清除(并发、标记结合使用)
使用方式:-XX:+UseConcMarkSweepGC
五、类加载器
若需要执行Java代码,则需要将代码加载到JVM的内存中,所以需要类加载器.类通常是按需加载,即第一次使用该类时才加载。
类加载流程:加载、链接(验证、准备、解析)、初始化
加载:取得类的二进制流,转为方法区数据结构
在Java堆中生成对应的java.lang.Class对象
链接 -> 验证:保证Class流的格式是正确的。包括文件格式的验证、元数据验证、
字节码验证、符号引用验证。
链接 -> 准备:分配内存,并为类设置初始值 (方法区中)
链接 -> 解析:符号引用替换为直接引用
初始化:执行父类构造函数,父类的static变量 赋值语句
static{}语句,然后再执行子类的构造函数,子类的static变量 赋值语句
static{}语句。
ClassLoader是一个抽象类,它的实例将读入Java字节码将类装载到JVM中。
可以定制,满足不同的字节码流获取方式。
ClassLoader分类:
BootStrap ClassLoader (启动ClassLoader,加载 rt.jar下的类)
Extension ClassLoader (扩展ClassLoader,加载 ext/*.jar下的类)
App ClassLoader (应用ClassLoader/系统ClassLoader ,加载Classpath下类)
Custom ClassLoader(自定义ClassLoader,加载自定义路径下的类)
ClassLoader默认使用双亲委派机制,即某个特定的类加载器在接到加载类的请求
时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加
载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
ClassLoader自底向上检查是否类已经加载,然后自顶向下尝试加载类。
tomcat加载类是先从底层ClassLoader加载的。
六、性能监控工具
系统性能监控
Linux环境下:
uptime : 查看系统时间、(1,5,15分钟内的系统平均负载)、运行时间等
top:动态查看进行运行情况
vmstat:查看CPU、内存、Swap等使用信息
pidstat:查看进程信息
Windows环境下:
任务管理器
自带的Perfmon
pslist:可显示指定程序运行情况 如:pslist javaw
Java自带工具
jps:列出java进程
jmap:生成Java应用程序的堆快照和对象的统计信息
jstack:打印线程信息,可以找出死锁信息
JConsole:图形化监控工具,可以查看Java应用程序的运行概况,监控堆信息、永久区使用情况、类加载情况等
Visual VM :是一个功能强大的多合一故障诊断和性能监控的可视化工具
七、Java堆分析
JVM使用内存空间的地方:堆、永久区、线程栈、直接内存。
内存溢出:内存不够,程序所需的内存超出了JVM内存所承受大小。
(1)堆溢出:增大堆空间,及时释放内存
(2)永久区生成大量的类:增大Perm区,允许Class回收
(3)栈溢出:减少线程栈大小
分析内存溢出:
(1)使用Memory Analyzer(MAT)工具分析
(2)Java自带工具分析
八、锁
使用锁,使多线程的访问时数据保持唯一性和准确性,与synchronized具有相同的效果。
对象头Mark:描述对象的hash、锁信息,垃圾回收标记,年龄
偏向锁:锁会偏向于当前已经占有锁的线程。在竞争不激烈时,可以通过偏向来提高
性能。使用方式:将对象头Mark的标记设置为偏向,并将线程ID写入对象头Mark,-XX:+UseBiasedLocking。
轻量级锁:普通的锁处理性能不够理想,轻量级锁是一种快速的锁定方法。
自旋锁:采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时才能进入临界区。
内置于JVM中的获取锁的优化方法和获取锁的步骤
偏向锁可用会先尝试偏向锁
轻量级锁可用会先尝试轻量级锁
以上都失败,尝试自旋锁
再失败,尝试普通锁,使用OS互斥量在操作系统层挂起
锁优化:减少锁持有时间、减小锁粒度(将大对象拆成小对象)、锁分离(如读锁写
锁分离)、锁粗化(在一些时候可扩大同步块范围)、锁消除(对不可能存在共享数据
竞争的锁进行消除)、无锁(某些时候可以不用锁)
九、Class文件结构
语言无关性:class文件内部定义了虚拟机可以识别的字节码格式,这个格式是
平台无关性的,在linux系统或者在windows系统上都是一致的,与语言也无关的。
Class文件结构是基于字节流的,用unicode进行编码
Class文件结构:
魔数
版本
常量池
访问符
类、超类、接口
字段
方法
属性
十、字节码执行
javap:Class文件反汇编工具
常用的字节码:常量入栈、局部变量压栈等等
使用ASM生成Java字节码:Java字节码操作框架,可以用于修改现有类或者动态产生新类
JIT:字节码执行性能较差,所以可以对于热点代码编译成机器码再执行,在运行时的编译。
JIT的基本思路是,将热点代码,就是执行比较频繁的代码,编译成机器码
字节码执行过程:常用的字节码:常量入栈、局部变量压栈等等
用ASM生成Java字节码:Java字节码操作框架,可以用于修改现有类或者动态产生新类
JIT:字节码执行性能较差,所以可以对于热点代码编译成机器码再执行,在运行时的编译。
JIT的基本思路是,将热点代码,就是执行比较频繁的代码,编译成机器码。