jdk包含jre,jre包含jvm
一、Java虚拟机分类
1. Sun Classic VM
- 第一款商用Java虚拟机
- 只能使用解释器方式执行java
2. Exact VM
- 编译器和解释器混合执行,两级即时编译器
- 只在Solaris平台发布,就被取代了
3. HotSpot VM
- 称霸武林
- 非sun开发,后收购的
4. KVM
- 手机平台运行
5. JRockit
- BEA公司
- 专注服务器端
6. J9
-IBM
7. Microsoft JVM
二、Java虚拟机内存结构
1.线程独占区
生命周期随着线程的创建而创建,随着线程的结束而死亡;所以不用关心这部分的垃圾回收。
- ①程序计数器:当前线程执行字节码行号指示器。
- ②虚拟机栈:一个个栈帧组成的,局部变量表、操作数栈、动态链接、方法出口信息;(每个方法执行,创建一个栈帧,伴随方法的创建到结束,存储局部变量表等,方法执行完毕,出栈。)
- ③本地方法栈:与虚拟机栈类似,不过提供的是native方法运行。
2.线程共享区
- ④堆:存储几乎所有的对象实例,虚拟机创建而创建。垃圾回收的主要区域。
- ⑤方法区:存储类信息,常量,静态变量。编译后的代码。
3.详解
① 程序计数器
- 一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
- 处于线程独占区,线程私有
- 此区域是Java虚拟机规范中,唯一没有规定任何OutOfMemoryError情况的区域。
- 生命周期随着线程的创建而创建,随着线程的结束而死亡。
②虚拟机栈
- Java方法执行的动态内存模型
- 栈放的是对象的引用
- 栈帧:每个方法的执行,都创建一个栈帧,伴随方法的创建到执行完成,用于存储局部变量表,操作数栈,动态链接,方法出口。
- 当方法在运行过程中需要创建局部变量时,就将局部变量的值存入栈帧的局部变量表中。
当这个方法执行完毕后,这个方法所对应的栈帧将会出栈,并释放内存空间。 - 栈内存溢出则,报错StackOverFlow(如递归)
注意:人们常说,Java的内存空间分为“栈”和“堆”,栈中存放局部变量,堆中存放对象。
这句话不完全正确!这里的“堆”可以这么理解,但这里的“栈”只代表了Java虚拟机栈中的局部变量表部分。真正的Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。
③本地方法栈
- 虚拟机栈为虚拟机执行Java方法服务,本地方法栈为虚拟机执行native方法服务。
- 本地方法栈和Java虚拟机栈实现的功能类似,只不过本地方法区是本地方法运行的内存模型。
④堆
- 存储对象实例,(几乎所有的对象都存储在堆中)
- 虚拟机启动时创建
- 垃圾收集器管理的主要区域
- 老年代,新生代;新生代又被分为Eden、From Survivor、To Survivor。不同区域生命周期不同,根据区域垃圾回收。
- -Xms -Xmx
⑤方法区
- 存储虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
- 方法区和永久代
- 垃圾回收在方法区的行为
- 异常的定义
- 运行时常量池,字符串会放到常量池里,类似hashset的table,如 String a = “aa”. String 的intern是把对象搬到运行时常量。
⑥ 直接内存(不属于虚拟机内存)
- 除Java虚拟机以外的内存,也有可能被Java使用。
- NIO, 在NIO中引入了一种基于通道和缓冲的IO方式。它可以通过调用本地方法直接分配Java虚拟机之外的内存,然后通过一个存储在Java堆中的DirectByteBuffer对象直接操作该内存,而无需先将外面内存中的数据复制到堆中再操作,从而提升了数据操作的效率。
- 直接内存大小不受Java虚拟机控制,不过,内存不足时就会抛出OOM异常。
综上所述
- Java虚拟机的内存模型中一共有两个“栈”,分别是:Java虚拟机栈和本地方法栈。
两个“栈”的功能类似,都是方法运行过程的内存模型。并且两个“栈”内部构造相同,都是线程私有。
只不过Java虚拟机栈描述的是Java方法运行过程的内存模型,而本地方法栈是描述Java本地方法运行过程的内存模型。 - Java虚拟机的内存模型中一共有两个“堆”,一个是原本的堆,一个是方法区。方法区本质上是属于堆的一个逻辑部分。堆中存放对象,方法区中存放类信息、常量、静态变量、即时编译器编译的代码。
4.可能出现内存溢出的情况
内存溢出的具体可能情况:
- 误用线程池,导致无限制创建线程,线程多导致每个线程的栈帧较大
- 查询数据量太大
- 动态生成类导致内存溢出
三、垃圾回收
1.如何判定对象为垃圾
- 引用计数法:在对象中添加一个引用计数器,当有地方引用这个对象的时候,引用计数+1,当引用失效引用计数-1.引用计数为0时回收。
- 可达性分析(当前使用) :根节点(GC Roots)不能到达的回收。GC Roots包括:
- 虚拟机栈中所引用的对象
- 本地方法栈中所引用的对象
- 方法区 类属性所引用的对象、常量所引用的对象
2. 标记垃圾对象方式
三色标记法: 具体是,黑白灰,
黑色为已标记的,灰色是正标记的,白色为未标记的。
3.回收方法
针对新生代: 通常复制算法
针对老年代: 标记-整理,标记清除。
(老年代对象一般较大不适合复制算法,浪费空间。标记-整理在可用对象多的情况下很适合。)
- 标记-清除算法
- 效率问题
- 空间问题
- 复制算法: (针对新生代)解决标记清除的效率问题;两块相同的内存,每次只用一块区域,回收时把不需要回收的部分复制到另一块内存,并连续排列,原来的内存区全部清空,下一次在这块区域继续划分,循环这种操作。
内存浪费的解决方案。新生代分为Eden,From Survivor,To Survivor,比例是8:1:1。我们认为垃圾回收后剩余大约10%的有用对象。在Eden的对象垃圾收集以后剩余的放在Survivor,From与To之间是复制算法的关系。当Survivor内存不够时就需要一个内存担保,Tenured Gen。放到老年代。
- 标记-整理算法 :针对老年代,整理以后清除。
- 分代收集算法:根据垃圾需要收集的多少决定用复制还是标记-整理算法
4. 垃圾回收器
1.常见垃圾收集器
新生代 | 老年代 | 特点 |
---|---|---|
serial | serial old | stop the world(STW停顿时间) 、单线程 |
parallel scavenge | parallel old | 注重吞吐量; stop the world(STW停顿时间)、多线程并行 |
ParNew | CMS | 可预测的停顿时间; gc线程与业务线程并发执行;cms无法处理浮动垃圾(标为不是垃圾后又变成垃圾的场景) ParNew是为了配置cms,与ps po特性一致 |
G1 | G1 | 兼顾吞吐量和停顿时间 |
ZGC | ZGC |
2.CMS特点
(1)CMS优点
- cms是一种获取最短回收停顿时间为目标的收集器;适合关注响应速度,希望停顿时间短,用户体验好
- gc与业务线程并发执行
- cms收集步骤:
- 初始标记(root直接关联的对象)-
- 并发标记(全部节点标记)-
- 重新标记(修正业务线程引起的变化节点)-
- 并发清除; 【初始标记和重新标记需要STW】
(2)CMS缺点
- 占用一部分线程导致程序变慢,降低总吞吐量
- 无法处理 浮动垃圾
- 标记清除方式,所以会产生碎片,有空间缺无法对大对象分配
3.CMS和G1的区别
G1计划作为并发标记扫描收集器(CMS)的长期替代品。
- 1.垃圾回收理念不同:CMS基于分代收集理念设计。G1基于分区收集理念设计。
- 2.整理:G1在GC的时候都会做垃圾的碎片整理,而CMS收集器只在Full GC STW时才会做内存压缩整理。
- 3.可停顿时间:G1是一种兼顾吞吐量和停顿时间的 GC 实现,其可靠停顿预测模型可以设定目标收集停顿时间,可以实现更短的GC停顿。
- 4.对象记录算法:对于对象记录CMS使用增量更新算法,而G1使用原始快照(SATB,snapshot-at-the-beginning)记录存活对象。
- 5.收集方式:G1使用混合收集的方式。G1可以扫描年轻代和一小部分老年代,但这意味着比简单地只扫描老年代、完全的快得多。
- 6.String重复数据删除。G1可以配置针对String的重复数据进行删除,而重复的数据将指向同一个char[] array。-XX:+UseStringDeduplication
- CMS对处理器资源非常敏感。CMS默认启动的回收线程数是(处理器数量+3)/4,因此弱核心数量在4个以上,占用内存不超过25%。若核心数量小于4,则占用内存过大。
- G1针对具有大内存的多处理器机器,因为其Remembered Sets的记忆集的设计,需要占用更多内存。
4.full gc频繁的可能原因
- 大对象创建频繁
- 内存泄漏(长期持有对象会进入老年代)
- 代码问题,程序执行了System.gc()
四、内存分配策略
1. 优先分配到eden
打印垃圾收集信息: -verbose:gc -XX:+PrintGCDetails
2. 大对象直接分配到老年代
指定大对象的大小: -XX:PretenureSizeThreshold=8M
新生代通常是复制算法,新生代垃圾回收的频率很高,所以放在老年代更好。
3. 长期存活的对象分配到老年代(jdk7以后不严格)
指定年龄多少算长期 -XX:MaxTenuringThreshold 15
年龄计数器,每次垃圾回收年龄+1
4. 空间分配担保
内存不够时借用老年代内存
需要检查老年代是否有足够的空间容纳全部的新生代
开启空间分配担保 -XX:+HandlePromotionFailure
禁用空间分配担保 -XX:-HandlePromotionFailure
5. 动态对象的年龄判断
6. 逃逸分析和栈上分配
逃逸分析:分析对象的作用域。
栈上分配是java虚拟机提供的一种优化技术,在栈上申请空间,而栈是函数运行完立即清理,所以不需要等到gc,大大缓解了gc的压力。
没有发生逃逸的对象放在栈内存中。
public class PartionOnStack {
static class User{
private int id;
private String name;
public User(){}
}
private static User user;
public static void foo() {
user=new User();
user.id=1;
user.name="sixtrees";
}
public static void main(String[] args) {
foo();
}
}
因为上面的代码中的User的作用域是整个Main Class,所以user对象是可以逃逸出函数体的。下面的代码展示的则是一个不能逃逸的代码段。
public class PartionOnStack {
class User{
private int id;
private String name;
public User(){}
}
public void foo() {
User user=new User();
user.id=1;
user.name="sixtrees";
}
public static void main(String[] args) {
PartionOnStack pos=new PartionOnStack();
pos.foo();
}
}
五、虚拟机工具
- Jps
- Java process status
- 控制台 打 "jps". 显示Java进程id
- jps -l 运行时主类全名或jar包名
- jps -m 运行时主类接收的参数(在args中)
- jps -v 接收的VM的参数
- 本地虚拟机唯一Id lvmid local virtual machine id
- Jstat
- 依赖jps, 需要知道进程的id才能使用
- 官网可查看文档
- jstat -gcutil {id} 垃圾回收概要信息
- Jinfo
- 实时查看和调整虚拟机的各项参数
- jinfo -flag UseSerialGC {id} 查看该进程是否使用SerialGC。是(+),否(-)
- Jmap
- jmap -dump:format=b,file=/Users/yuan/a.bin {进程id} 转储快照
- jmap -histo {id} 类和实例信息
- Jhat
- 分析Jmap快照
- jhat {文件路径}
- 启动http server
- Jstack
- 线程快照
- Jstack {id}
- JConsole
- 内存监控
- 线程监控
- 死锁检测
- VisualVM 需下载
六、性能调优
案例1:绩效考核系统
- 环境:
64G, 2个intel E5 CPU
tomcat7, jdk7
堆内存设置了50G - 问题:经常卡顿
- 处理思路:
优化sql(不是每个功能慢了,是某个时间段慢了)监控CPU- 监控内存
- Full GC 20-30s
-
解决:
部署多个web容器,每个web容器堆内存设置为4G.用nginx负载。 这样解决了堆内存多大垃圾收集时间长。又不浪费内存。 -
总结:
没有大对象,不经常Full GC,部署多个容器更好。否则单个容器分配大的内存更好。毕竟容器启动会有额外内存,硬盘开销。
案例2:数据抓取系统
-
环境
jdk5, 2G内存, intel core i3, win server -
问题
不定期内存溢出,堆内存加大也无济于事,导出堆快照,没人信息。内存监控,正常 -
处理思路
捕获到了bytebuffer。系统用了很多NIO。direct memory改大一些。
案例3:物联网应用
JVM崩溃。 Connect Reset
任务挤压,大量未处理任务导致
解决:加消息队列
七、类加载
1.类加载过程
三个过程:
- 1.加载
①将类的字节码载入方法区,并创建类.class对象
②如果类的父类没有加载先加载父类
③加载是懒惰执行 - 2.链接
① 验证-验证类是否符合Class规范,合法性、安全性检查
② 准备-为static变量分配空间,设置默认值
③ 解析-将常量池的符号引用解析为直接引用 - 3.初始化
①执行静态代码块和非final静态变量的赋值
②初始化是懒惰执行