JVM

jdk包含jre,jre包含jvm

初试,分析解决

查看堆存储快照
VM arguments

-XX:+HeapDumpOnOutOfMemoryError

设置堆内存大小

-Xms20m -Xmx20m

分析工具:eclipse memory analyzer

JVM监控工具

jdk/bin/jconsole 直接运行
堆内存,分为新生代和老年代。垃圾回收会对新生代进行回收。新生代分为,Eden和两个Survivor。
垃圾回收会回收一部分Eden,存活的进入到Survivor生存区。存活久的进入到老年代。

Java虚拟机

Sun Classic VM

  • 第一款商用Java虚拟机
  • 只能使用解释器方式执行java

Exact VM

  • 编译器和解释器混合执行,两级即时编译器
  • 只在Solaris平台发布,就被取代了

HotSpot VM

  • 称霸武林
  • 非sun开发,后收购的

KVM

  • 手机平台运行

JRockit

  • BEA公司
  • 专注服务器端

J9

-IBM

Microsoft JVM

Java虚拟机内存结构

分为:

线程独占区
生命周期随着线程的创建而创建,随着线程的结束而死亡;所以不用关心这部分的垃圾回收。

  • 程序计数器:当前线程执行字节码行号指示器。
  • 虚拟机栈:一个个栈帧组成的,局部变量表、操作数栈、动态链接、方法出口信息;每个方法执行,创建一个栈帧,伴随方法的创建到结束,存储局部变量表等,方法执行完毕,出栈。
  • 本地方法栈:与虚拟机栈类似,不过提供的是native方法运行。

线程共享区

  • 堆:存储几乎所有的对象实例,虚拟机创建而创建。垃圾回收的主要区域。
  • 方法区:存储类信息,常量,静态变量。编译后的代码。

程序计数器

  • 一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
  • 处于线程独占区,线程私有
  • 此区域是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虚拟机的内存模型中一共有两个“堆”,一个是原本的堆,一个是方法区。方法区本质上是属于堆的一个逻辑部分。堆中存放对象,方法区中存放类信息、常量、静态变量、即时编译器编译的代码。

对象的创建

1、new 类名
2、根据new的参数在常量池中定义一个类的引用符号。
3、如果没有找到这个符号引用,说明类还没有被加载,进行类的加载,解析和初始化。
4、虚拟机为对象分配内存,(堆)
5、将分配的内存初始化为零值(不包括对象头)
6、调用对象的init方法。(代码块,构造方法)

给对象分配内存

  • 指针碰撞(指针移动),连续的空间。
  • 空闲列表:一张表,记录空闲内存。在空闲的部分分配内存。
  • 线程安全
    • 加锁
    • 每个线程单独分配自己的区域,叫做本地线程分配缓冲

对象的结构

  • Header
    • 自身运行时数据(Mark Word)
      • 哈希值
      • GC分代年龄
      • 锁状态标志
      • 线程持有的锁
      • 偏向线程ID
      • 偏向时间戳
    • 类型指针
  • InstanceData
  • Padding

对象的访问定位

栈的对象引用指向堆的对象实例。

  • 使用句柄:指向堆中的句柄池,句柄池指向对象的地址
  • 直接指针:直接指向对象的地址(hotspot)

具体哪一种方式,跟虚拟机有关。

垃圾回收

如何判定对象为垃圾

  • 引用计数法:在对象中添加一个引用计数器,当有地方引用这个对象的时候,引用计数+1,当引用失效引用计数-1.引用计数为0时回收。
  • 可达性分析:根节点(GC Roots)不能到达的回收。GC Roots包括:
    • 虚拟机栈
    • 方法区的类属性所引用的对象
    • 方法区中常量所引用的对象
    • 本地方法栈中所引用的对象

如何回收

回收策略

新生代通常复制算法,老年代标记-整理,标记清除。老年代对象一般较大不适合复制算法,浪费空间。标记-整理在可用对象多的情况下很适合。

  • 标记-清除算法
    • 效率问题
    • 空间问题
  • 复制算法:(针对新生代)解决标记清除的效率问题;两块相同的内存,每次只用一块区域,回收时把不需要回收的部分复制到另一块内存,并连续排列,原来的内存区全部清空,下一次在这块区域继续划分,循环这种操作。

内存浪费的解决方案。新生代分为Eden,From Survivor,To Survivor,比例是8:1:1。我们认为垃圾回收后剩余大约10%的有用对象。在Eden的对象垃圾收集以后剩余的放在Survivor,From与To之间是复制算法的关系。当Survivor内存不够时就需要一个内存担保,Tenured Gen。放到老年代。

  • 标记-整理算法:针对老年代,整理以后清除。
  • 分代收集算法:根据垃圾需要收集的多少决定用复制还是标记-整理算法

常见回收器(Java虚拟机没有规范,下面的不同收集器适用不同的场景)

  • Serial(新生代)
    • 最基本,发展最悠久的
    • 单线程(多线程任务执行,垃圾收集时全部线程暂停等待单线程的收集,收集完继续多个线程的执行)
    • 桌面应用
  • ParNew(新生代)
    • 多线程
  • Parallel Scavenge
    • 复制算法(新生代收集器)
    • 多线程收集器
    • 吞吐量:(执行用户代码的时间) / 总时间
    • 达到可控制的吞吐量
    • -XX:MaxGCPauseMillis 垃圾收集器最大停顿时间
    • -XX:GCTimeRatio 吞吐量大小, 取值(0, 100)
  • CMS(Concurrent Mark Sweep)
    • 针对老年代
  • G1
    • 面向服务端
    • 综合前面的优势
    • 并行和并发
    • 分代收集
    • 空间整合
    • 可预测的停顿
    • 标记整理

何时回收

内存分配策略

1. 优先分配到eden

打印垃圾收集信息: -verbose:gc -XX:+PrintGCDetails

2. 大对象直接分配到老年代

指定大对象的大小: -XX:PretenureSizeThreshold=8M
新生代通常是复制算法,新生代垃圾回收的频率很高,所以放在老年代更好。

3. 长期存活的对象分配到老年代(jdk7以后不严格)

指定年龄多少算长期 -XX:MaxTenuringThreshold 15
年龄计数器,每次垃圾回收年龄+1

4. 空间分配担保

内存不够时借用老年代内存
需要检查老年代是否有足够的空间容纳全部的新生代
开启空间分配担保 -XX:+HandlePromotionFailure
禁用空间分配担保 -XX:-HandlePromotionFailure

5. 动态对象的年龄判断

6. 逃逸分析和栈上分配

逃逸分析:分析对象的作用域。
栈上分配是java虚拟机提供的一种优化技术

只在方法体内部有效,不发生逃逸。反之。
为成员属性赋值,发生逃逸;
对象的作用域仅在当前方法有效,没有发生逃逸。
引用成员变量的值,发生逃逸。

没有发生逃逸的对象放在栈内存中。

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

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


Class文件

编译器编译产生的。字节码文件
.class文件需要二进制编辑器打开。比如binary viewer

文件结构

Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列。中间没有任何分隔符。
Class文件中有两种数据类型:无符号数和表。

第一行前8位是魔数,后8位是版本号。
魔数: class文件的标识,CA FE BA BE, 魔数后面8位为版本号:JDK1.8 = 52, JDK1.7 = 51

常量池
访问标志
类索引,弗雷索引,接口索引集合
字段表集合
方法表集合
属性表集合

你可能感兴趣的:(Java)