Skip to content

Latest commit

 

History

History
272 lines (246 loc) · 30.1 KB

JVM-自动内存管理机制.md

File metadata and controls

272 lines (246 loc) · 30.1 KB

JVM学习之自动内存管理机制

1. 运行时数据区域

1.1 程序计数器

  • 程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器
  • 每一个线程都有一个独立的程序计数器,且互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

1.2 JAVA虚拟机栈

  • 与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。
  • JAVA方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
  • 每一个方法从调用到完成,对应着一个栈帧在虚拟机栈中的入栈出栈
  • 在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

1.3 本地方法栈

  • 虚拟机栈为JAVA(字节码)方法服务,本地方法栈为Native方法服务

1.4 Java堆

  • JAVA堆是被所有线程所共享的一块内存区域,在虚拟机启动时创建,唯一目的就是存放对象实例,不过随着JIT的发展与逃逸技术,栈上分配,标量替换优化技术将导致一些微妙的变化,只在堆上存放对象实例也不是那儿么‘绝对’
  • JAVA堆可以细分为Eden空间、From Survivor 空间、To Survivor空间等,划分的目的就是为更好的进行回收何分配

1.5 方法区

  • 方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。

1.6 运行时常量池

  • 运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

1.7 直接内存

  • 直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,堆外内存。

2.HotSpot虚拟机

2.1对象的创建

  • 创建对象时,虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过,检查通过,Java堆进行内存分配,根据采用的是什么垃圾回收决定堆是否规整,是采用'指针碰撞',否则采用空闲列表
  • 解决并发创建对象问题,一种是对分配内存空间的动作进行同步处理——实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;另一种是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲

2.2 对象的内存布局

  • 对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)
    • 对象头(Header):两部分,一部分为哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,官方称为:Mark Word
    • 对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
    • 第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或者2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

2.3 对象的访问定位

  • 建立对象是为了使用对象,我们的Java程序需要通过栈上的reference数据来操作堆上的具体对象。由于reference类型在Java虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位、访问堆中的对象的具体位置,所以对象访问方式也是取决于虚拟机实现而定的。目前主流的访问方式有使用句柄和直接指针两种。
  • 使用句柄访问对象
    • 使用句柄来访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改
  • 使用直接指针访问对象
    • 使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。就本书讨论的主要虚拟机Sun HotSpot而言,它是使用第二种方式进行对象访问的,但从整个软件开发的范围来看,各种语言和框架使用句柄来访问的情况也十分常见

2.4 实战:OutOfMemoryError异常

2.4.1 Java堆溢出

  • Args:-Xms20m-Xmx20m-XX:+HeapDumpOnOutOfMemoryError
    • -xms xmx 设置Java堆的大小
    • MaxPermSize(最大方法区容量)
    • +HeapDumpOnOutOfMemoryError 生成dump文件
    • -Xoss参数(设置本地方法栈大小)存在,但实际上是无效的,栈容量只由-Xss参数设定

2.4.2 虚拟机栈和本地方法栈溢出

  • 在Java虚拟机规范中描述了两种异常:
    • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
    • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。 - 如果是建立过多线程导致的内存溢出,在不能减少线程数或者更换64位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。如果没有这方面的处理经验,这种通过“减少内存”的手段来解决内存溢出的方式会比较难以想到。

2.4.3 方法区和运行时常量池溢出

  • string.intern() string.intern()是一个native方法,它的作用是:如果字符串常量池中已经包含一个等于此string对象的字符串,则返回代表池中这个字符串的string对象;否则,将此string对象包含的字符串添加到常量池中,并且返回此string对象的引用。
  • -xx:permsize和-xx:maxpermsize限制方法区大小
at java.lang.String.intern(Native Method)```
#### 2.4.4本机直接内存溢出 ####
- DirectMemory容量可通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值(-Xmx指定)一样

## 第3章 垃圾收集器与内存分配策略 ##
### 3.1 概述 ###
- 当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
- 程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭,
JAVA堆和方法区不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样。
### 3.2 对象已死 ###
- 堆里存放的几乎所有的对象,如何判断对象是否还存活
#### 3.2.1 引用计数算法 ####
- 实现简单,判定效率高,大部分情况下是一个不错的算法,微软公司的COM技术,使用ActionScript3的FlashPlayer,Python语言等
- 缺点很难解决对象之间相互循环引用的问题
#### 3.2.2 可达性分析 ####
- 在主流的商用程序语言(Java、C#,甚至包括前面提到的古老的Lisp)的主流实现中,都是称通过可达性分析(Reachability Analysis)来判定对象是否存活的。
- 个算法的基本思路就是通过一系列的称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的
- 可作为GC ROOTS 的对象包含下面
   - 虚拟机栈(栈帧中的本地变量表)中引用的对象。
   - 方法区中类静态属性引用的对象。
   - 方法区中常量引用的对象。
   - 本地方法栈中JNI(即一般说的Native方法)引用的对象。
![](https://i.imgur.com/h456laQ.png)
#### 3.2.3 引用 ####
- Java中的引用的定义很传统:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。
- 强引用:就是指在程序代码之中普遍存在的,类似"Object obj=new Object()"这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
- 软引用:是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK 1.2之后,提供了SoftReference类来实现软引用。
- 弱引用:也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引用。
- 虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2之后,提供了PhantomReference类来实现虚引用。
#### 3.2.5 回收方法区 ####
- Java虚拟机规范中确实说过可以不要求虚拟机在方法区实现垃圾收集,而且在方法区中进行垃圾收集的“性价比”一般比较低:在堆中,尤其是在新生代中,常规应用进行一次垃圾收集一般可以回收70%~95%的空间,而永久代的垃圾收集效率远低于此。
- 永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类
   - 回收废弃常量:与回收Java堆中的对象非常类似。以常量池中字面量的回收为例,假如一个字符串"abc"已经进入了常量池中,但是当前系统没有任何一个String对象是叫做"abc"的,换句话说,就是没有任何String对象引用常量池中的"abc"常量,也没有其他地方引用了这个字面量,如果这时发生内存回收,而且必要的话,这个"abc"常量就会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。
   - 判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面3个条件才能算是“无用的类”:
      - 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
      -  加载该类的ClassLoader已经被回收。
      -  该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
   -  是否对类进行回收,HotSpot虚拟机提供了-Xnoclassgc参数进行控制,还可以使用-verbose:class以及-XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看类加载和卸载信息,其中-verbose:class和-XX:+TraceClassLoading可以在Product版的虚拟机中使用,-XX:+TraceClassUnLoading参数需要FastDebug版的虚拟机支持。
### 3.3 垃圾收集算法 ###
- 各个平台的虚拟机操作内存的方法又各不相同
#### 3.3.1 标记-清除算法 ####
- 算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,它的标记过程其实在前一节讲述对象标记判定时已经介绍过了
- 一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。标记—清除算法的执行过
![](https://i.imgur.com/jytEFjE.png)
#### 3.3.2 复制算法 ####
- 将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这块使用完了,将活着的对象复制到另外一块,清理掉使用过的内存空间,这样使得每次回收都是整个内存半区回收,缺点:将内存缩小为原来的一半!
![](https://i.imgur.com/k9cawu8.png)
![](https://i.imgur.com/2fas9Wj.png)
#### 3.3.3 标记-整理算法 ####
- 标记方法同"标记-清除算法"一致,但是再垃圾回收时,并不直接回收,而是将存活的对象移动到一块,然后在统一清除

![](https://i.imgur.com/M05kFOA.png)/home/care4u

#### 3.3.4 分代收集算法 ####
- 根据对象的存活周期将内存划分几块区域,一般JAVA 堆分为新生代和老年代 ,新生代中每次收集大批对象死亡,所以采用复制算法。老年代中对象存活比较长,所以采用标记算法
### 3.4 HotSpot的算法实现 ###
#### 3.4.1 枚举根节点 ####
- 从可达性分析中从GC Roots节点找引用链这个操作为例,可作为GC Roots的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(例如栈帧中的本地变量表)中,现在很多应用仅仅方法区就有数百兆,如果要逐个检查这里面的引用,那么必然会消耗很多时间。
#### 3.4.2 安全点 ####
- HotSpot也的确没有为每条指令都生成OopMap,前面已经提到,只是在“特定的位置”记录了这些信息,这些位置称为安全点(Safepoint),即程序执行时并非在所有地方都能停顿下来开始GC,只有在到达安全点时才能暂停。
- 安全点的选择:指令序列复用,例如方法调用、循环跳转、异常跳转等,所以具有这些功能的指令才会产生Safepoint。
#### 3.4.3 安全区域 ####
- 安全区域是指在一段代码片段之中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。我们也可以把Safe Region看做是被扩展了的Safepoint。
## 3.5 垃圾收集器 ##
- 如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现
- 这个虚拟机所包含的所有收集器
![](https://i.imgur.com/I2GK0BP.png)
### 3.5.1 Serial收集器 ###
- 这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束
![](https://i.imgur.com/bUBcDx9.png)
### 3.5.2 ParNew收集器 ###
- ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样,在实现上,这两种收集器也共用了相当多的代码。
![](https://i.imgur.com/B0TMIbc.png)

>**●并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。**

>**●并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。**
### 3.5.3 Parallel Scavenge收集器 ###
- Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器
- Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
- Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。
- -XX:+UseAdaptiveSizePolicy 打开这个参数后不需要指定,新生代大小、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了
- 自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别。
### 3.5.4 Serial Old收集器 ###
- Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。这个收集器的主要意义也是在于给Client模式下的虚拟机使用
![](https://i.imgur.com/gK19xsQ.png)
### 3.5.5 Parallel Old收集器 ###
- Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法
![](https://i.imgur.com/UiE9rBI.png)
### 3.5.6 CMS收集器 ###
- CMS 收集器是一种以获取最短回收停顿时间为目标的收集器。响应速度快
	- 初始标记(CMS initial mark)

	  	并发标记(CMS concurrent mark)

		重新标记(CMS remark)

		并发清除(CMS concurrent sweep)
	-	初始标记和并发标记,仍然需要停止。初始标记只是标记一下GC root能直接关联到的对象
![](https://i.imgur.com/0t5RBY1.png)
- 缺点:
   - 1.CMS收集器对CPU资源非常敏感。其实,面向并发设计的程序都对CPU资源比较敏感,CMS默认启动的回收线程数是(CPU数量+3)/4
   - 2.CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现"Concurrent Mode Failure"失败而导致另一次Full GC的产生
   - --	-XX:CMSInitiatingOccupancyFraction 设置老年代使用多少空间时激活CMS收集器,太高容易导致回收失败,性能降低
   - 采用‘标记-清除’算法,碎片空间过多,对于大对象的分配很麻烦,往往老年代还有很大的空间,但是无法找到连序的空间进行分配
   - XX:+UseCMSCompactAtFullCollection用于CMS收集器顶不住要进行FullGC时开启内存碎片的合并整理过程,内存整理的过程是无法并发的,空间碎片问题没有了,但停顿时间不得不变长。
   - -XX:CMSFullGCsBeforeCompaction,这个参数是用于设置执行多少次不压缩Full GC后,跟着来一次带压缩的(默认值为0,表示每次进入Full GC时都进行碎片整理)
### 3.5.7 G1收集器 ###
- G1是一款面向服务端应用的垃圾收集器
   - 并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。
   - 分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
   - 空间整合:与CMS的“标记—清理”算法不同,G1从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。
   - 可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了
   - 初始标记(Initial Marking)

		并发标记(Concurrent Marking)
		
		最终标记(Final Marking)
		
		筛选回收(Live Data Counting and Evacuation)
![](https://i.imgur.com/yWozlLF.png)

### 垃圾收集相关参数 ###
![](https://i.imgur.com/yv41vse.png)
![](https://i.imgur.com/PIOBxUs.png)
## 3.6 内存分配与回收策略 ##
- 给对象分配内存以及回收分配给对象的内存
###  3.6.1 对象优先在Eden分配 ###
- 大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
- -XX:+PrintGCDetails 开启打印内存回收日志,并且在内存退出的时候输出当前内存的分配情况
- 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。

- 老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。
### 3.6.2 大对象直接进入老年代 ###
- 所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组
- -XX:PretenureSizeThreshold 大于该对面值得对面直接在老年代分配,避免Eden区以及Survivor 区域发生大量的内存复制
### 3.6.3 长期存活的对象将进入老年代 ###
- 在Eden区域出生,经过第一次minor GC后,能够移动到survivor空间,并且对象年龄设为1,经过15次minor GC 后进入老年代(默认)
- -XX:MaxTenuringThreshold 设置年龄
### 3.6.4 动态对象年龄判定 ###
- survivor空间中相同年龄的对象大小和大于survivor一半,年龄大于等于该年龄的对象直接进入老年代
### 3.6.5 空间分配担保 ###
- 查看老年代连序空间是否大于新生代所有对象空间,成立进行minor GC
如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败
- 如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。
## 第4章 虚拟机性能监控与故障处理工具 ##
### 4.1 概述 ###
- 适当合理的运用工具,尽心内存的分析。给一个系统定位问题的时候,知识、经验是关键基础,数据是依据,工具是运用知识处理数据的手段。这里说的数据包括:运行日志、异常堆栈、GC日志、线程快照(threaddump/javacore文件)、堆转储快照(heapdump/hprof文件)等
### 4.2 JDK命令行工具 ###
![](https://i.imgur.com/jL8oXRc.png)
![](https://i.imgur.com/419WU2U.png)
#### 4.2.1 jps:虚拟机进程状况工具 ####
![](https://i.imgur.com/hVBazxb.png)
#### 4.2.2 jstat:虚拟机统计信息监视工具 ####
- jstat(JVM Statistics Monitoring Tool)是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程[1]虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。
- `jstat[option vmid[interval[s|ms][count]]] `
![](https://i.imgur.com/eRzhD9F.png)
#### 4.2.3 jinfo:Java配置信息工具 ####
- jinfo(Configuration Info for Java)的作用是实时地查看和调整虚拟机各项参数。使用jps命令的-v参数可以查看虚拟机启动时显式指定的参数列表,但如果想知道未被显式指定的参数的系统默认值,除了去找资料外,就只能使用jinfo的-flag选项进行查询了
#### 4.2.4 jmap:Java内存映像工具 ####
- jmap(Memory Map for Java)命令用于生成堆转储快照(一般称为heapdump或dump文件)
- jmap的作用并不仅仅是为了获取dump文件,它还可以查询finalize执行队列、Java堆和永久代的详细信息,如空间使用率、当前用的是哪种收集器等。
![](https://i.imgur.com/eZDVk0g.png)
#### 4.2.5 jhat:虚拟机堆转储快照分析工具 ####
- n JDK提供jhat(JVM Heap Analysis Tool)命令与jmap搭配使用,来分析jmap生成的堆转储快照。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,可以在浏览器中查看。
- jhat [file]
#### 4.2.6 jstack:Java堆栈跟踪工具 ####
- jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者javacore文件)。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。
![](https://i.imgur.com/uNRP3Ty.png)
#### 4.2.7 HSDIS:JIT生成代码反汇编 ####

![](https://i.imgur.com/e7jNM4T.png)
1)虚拟机启动参数只限制了Java堆为100MB,没有指定-Xmn参数,能否从监控图中估计出新生代有多大?
> 图4-6显示Eden空间为27 328KB,因为没有设置-XX:SurvivorRadio参数,所以Eden与Survivor空间比例为默认值8:1,整个新生代空间大约为27 328KB×125%=34 160KB。

2)为何执行了System.gc()之后,图4-6中代表老年代的柱状图仍然显示峰值状态,代码需要如何调整才能让System.gc()回收掉填充到堆中的对象?

>执行完System.gc()之后,空间未能回收是因为List<OOMObject>list对象仍然存活,fillHeap()方法仍然没有退出,因此list对象在System.gc()执行时仍然处于作用域之内[2]。如果把System.gc()移动到fillHeap()方法外调用就可以回收掉全部内存。


### 4.3 JDK的可视化工具 ###
#### 4.3.1 JConsole:Java监视与管理控制台 ####
- JConsole(Java Monitoring and Management Console)是一种基于JMX的可视化监视、管理工具。它管理部分的功能是针对JMX MBean进行管理,由于MBean可以使用代码、中间件服务器的管理控制台或者所有符合JMX规范的软件进行访问,所以本节将会着重介绍JConsole监视部分的功能。
#### 4.3.2 VisualVM:多合一故障处理工具 ####
- VisualVM(All-in-One Java Troubleshooting Tool)是到目前为止随JDK发布的功能最强大的运行监视和故障处理程序,并且可以预见在未来一段时间内都是官方主力发展的虚拟机故障处理工具。
![](https://i.imgur.com/yRCMwq2.png)
- 启动: jvisualvm.exe
![](https://i.imgur.com/RrPqw1x.png)

- 2.生成、浏览堆转储快照  
   - 在VisualVM中生成dump文件有两种方式,可以执行下列任一操作:

	   	在“应用程序”窗口中右键单击应用程序节点,然后选择“堆Dump”。

		在“应用程序”窗口中双击应用程序节点以打开应用程序标签,然后在“监视”标签中单击“堆Dump”。
   - 需要保存则右键直接另存为保存下来
   ![](https://i.imgur.com/1AETBLX.png)
-  3.分析程序性能 
   -  在Profiler页签中,VisualVM提供了程序运行期间方法级的CPU执行时间分析以及内存分析,做Profiling分析肯定会对程序运行性能有比较大的影响,所以一般不在生产环境中使用这项功能。
   -  CPU 统计每个方法执行次数、执行耗时;
   -  内存分析,则会统计每个方法关联的对象数以及这些对象所占的空间
-  4.BTrace动态日志跟踪
   -  BTrace[3]是一个很“有趣”的VisualVM插件,本身也是可以独立运行的程序。它的作用是在不停止目标程序运行的前提下,通过HotSpot虚拟机的HotSwap技术[4]动态加入原本并不存在的调试代码
![](https://i.imgur.com/fuevrtE.png)
## 5.2 案例分析 ##
### 5.2.1 高性能硬件上的程序部署策略 ###

- 在高性能硬件上部署程序,目前主要有两种方式:
> - 通过64位JDK来使用大内存。

> - 使用若干个32位虚拟机建立逻辑集群来利用硬件资源。
### 5.2.2 集群间同步导致的内存溢出 ###