当前位置:   article > 正文

JVM笔记_java 虚拟机的类型

java 虚拟机的类型

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

二、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.常见垃圾收集器

在这里插入图片描述

新生代老年代特点
serialserial oldstop the world(STW停顿时间) 、单线程
parallel scavengeparallel old注重吞吐量; stop the world(STW停顿时间)、多线程并行
ParNewCMS可预测的停顿时间; gc线程与业务线程并发执行;cms无法处理浮动垃圾(标为不是垃圾后又变成垃圾的场景) ParNew是为了配置cms,与ps po特性一致
G1G1兼顾吞吐量停顿时间
ZGCZGC
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();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

因为上面的代码中的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();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

五、虚拟机工具

- 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

六、性能调优

案例1:绩效考核系统
  1. 环境:
    64G, 2个intel E5 CPU
    tomcat7, jdk7
    堆内存设置了50G
  2. 问题:经常卡顿
  3. 处理思路:
  • 优化sql(不是每个功能慢了,是某个时间段慢了)
  • 监控CPU
  • 监控内存
    • Full GC 20-30s
  1. 解决:
    部署多个web容器,每个web容器堆内存设置为4G.用nginx负载。 这样解决了堆内存多大垃圾收集时间长。又不浪费内存。

  2. 总结:
    没有大对象,不经常Full GC,部署多个容器更好。否则单个容器分配大的内存更好。毕竟容器启动会有额外内存,硬盘开销。

案例2:数据抓取系统
  1. 环境
    jdk5, 2G内存, intel core i3, win server

  2. 问题
    不定期内存溢出,堆内存加大也无济于事,导出堆快照,没人信息。内存监控,正常

  3. 处理思路
    捕获到了bytebuffer。系统用了很多NIO。direct memory改大一些。

案例3:物联网应用

JVM崩溃。 Connect Reset

任务挤压,大量未处理任务导致
解决:加消息队列

七、类加载

1.类加载过程

三个过程:

  • 1.加载
    ①将类的字节码载入方法区,并创建类.class对象
    ②如果类的父类没有加载先加载父类
    ③加载是懒惰执行
  • 2.链接
    ① 验证-验证类是否符合Class规范,合法性、安全性检查
    ② 准备-为static变量分配空间,设置默认值
    ③ 解析-将常量池的符号引用解析为直接引用
  • 3.初始化
    ①执行静态代码块和非final静态变量的赋值
    ②初始化是懒惰执行
1.1 双亲委派模型

在这里插入图片描述

1.2 能自己写个类叫java.lang.System吗

在这里插入图片描述

2. 对象引用类型的分类

在这里插入图片描述

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号