JVM解析
转载:深入理解JVM 作者:Utiopia
WORA:write once,run anywhere(一次编写,到处运行)
----------------------------------------------------------------------------------------
JVM调用方法有五条指令:
- invokeinterface 调用接口方法
- invokespecial 调用初始化方法、私有方法、或父类中定义得方法
- invokestatic 调用静态方法
- invokevirtual 调用实例方法
-----------------------------------------------------------------------------------------
看视频可能回好点:B站 深入理解JVM
---------------------------------------------------------------------------------------------------
6.10
1) 虚拟机参数快照-->分析(Memory Analyzer)
2) JConsole
3) lambda 表达式 转载:Java中Lambda表达式的使用 作者:Franson
4) Sun Classic VM 第一款商用java虚拟机 只能使用纯解释器的方式来执行java代码
-----------------------------------------------------------------------------------------------------
6.11
1) Exact VM 准确式内存管理 编译器和解释器混合工作以及两级即时编译器
2) Hotspot VM
3) KVM 嵌入式虚拟机
4) JRoick VM
5) J9
6) Dalvik VM
7) Microsoft JVM
8) Azul VM Liquid VM
9) TaobaoVM
10) Java虚拟机内存管理
线程共享区:
线程独占区:
程序计数器:
- 是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器
- 线程独占区
- 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执 行的虚拟机字节码指令的地址
- 如果正在执行的是Native方法,这个计 数器值则为空(Undefined)
- 此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
java虚拟机栈
- 虚拟机栈描述的是Java方 法执行的内存模型:每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
- 每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
- 局部变量表存放了编译期可知的各种基本数据类型和引用类型,局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
本地方法栈
- 虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务
Java堆
- 此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
- Java堆是垃圾收集器管理的主要区域
- 从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。
方法区
- 是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译 器编译后的代码等数据。
- 很多人都更愿意把方法区称为“永久代”
运行时常量池
- 是方法区的一部分。
- 用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
为true 字节码常量
'
不存在常量池 为false
直接内存
- 并不是虚拟机运行时数据区的一部分, 也不是Java虚拟机规范中定义的内存区域。
11) 对象的创建
Thread Local Allocation Buffer TLAB 本地线程分配缓存
对象的结构:
对象头(Header):
- 包括两部分信息,第一部分用于存储对象 自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
- 对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
数据实例(InstanceData)
- 实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。
- 从分配策略中可以看出,相同宽度的字段总是被分配到一起。在满足这个前提条件的情况 下,在父类中定义的变量会出现在子类之前。
填充(Pading)
------------------
对象的访问定位
---------------
垃圾回收
- 哪些内存需要回收
- 什么时候回收
- 如何回收
如何判定对象为垃圾对象
- 引用计数法 很多教科书判断对象是否存活的算法是这样的:给对象中添加一个 引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效 时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。但是它很难解决对象之间相互循环引用的问题。
- 可达性分析算法 的基本思路就是通过一系列的称 为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走 过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有 任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的
如何回收
- 标记-清除算法
- 复制算法
- 标记-整理算法
- 分代收集算法
垃圾回收器
- Serial 单线程收集器
- ParNew
- Parallel Scavenge
- CMS Concurrent Mark Sweep
- G1
G1 收集器
- 是当今收集器技术发展的最前沿成果之 一
- G1是一款面向服务端应用的垃圾收集器
- 并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用 多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿的时间,部 分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可 以通过并发的方式让Java程序继续执行。
- 分代收集:与其他收集器一样,分代概念在G1中依然得以保留。 虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
- 空间整合:与CMS的“标记—清理”算法不同,G1从整体来看是基 于“标记—整理”算法实现的收集器,从局部(两个Region之间)上来看 是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存
- 可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间 是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测 的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段 内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时 Java(RTSJ)的垃圾收集器的特征了。
G1步骤:
- 初始标记
- 并发标记
- 最终标记
- 刷选回收
--------------------------------------------------------------------------------------------------------------
6.12
1) 内存分配与回收策略
- 对象优先在Eden分配
Minor GC和Full GC:新生代(Minor GC) 老年代(Full GC)
- 大对象直接分配到老年代
- 长期存活的对象进入老年代
- 动态对象年龄判定
- 空间分配担保
2) 逃逸分析和栈上分配
3) 虚拟机工具
工具是运用知识处理数据的手段。这里说的数据包括:运行日志、 异常堆栈、GC日志、线程快照(threaddump/javacore文件)、堆转储快照(heapdump/hprof文件)等
- Jps
- Jstat
- Jinfo
- Jhat
- Jstack
- Jconsole
4) Jps JVM Process Status Tool 显示指定系统内所有的HotSpot虚拟机进程
本地虚拟机唯一ID(Local Virtual Machine Identifier)
5) Jstat JVM Statistics Monitoring Tool 用于监视虚拟机各种运行状态信息的命令行工具
6) Jinfo Configuration Info for Java 实时地查看和调整虚拟机各项参数
7) jmap Memory Map for Java 用于生成堆转储快照(一般称为 heapdump或dump文件)
8) Jhat JVM Heap Analysis Tool 虚拟机堆转储快照分析工具
9) jstack Stack Trace for Java Java堆栈跟踪工具
10) HSDIS JIT生成代码反汇编
11) JDK可视化工具
JConsole Java监视与管理控制台
VisualVM 多合一故障处理工具
--------------------------------------------------------------------------------------------------------------------
6.13
案例调优
1) 案例一 绩效考核项目
问题:
- 经常用用户反映长时间出现卡顿的现象
处理思路:
- 优化sql---------------------->不是
- 监控CPU-------------------->不是
- 监控内存 Full GC(20-30s)
解决方案:
- 部署多个web容器,每个web容器的堆内存指定为4g
2) 案例二:数据抓取
问题:
- 不定期内存溢出,把堆内存加大,也没用,导出堆存储快照信息,没有任何信息。内存监控正常
处理思路:
- 是机器问题,换8g内存
3) 案例三:智能家居
问题:
- JVM崩溃
处理思路:
- 使用消息队列来处理
4) Binary View
5) Class类文件的结构
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目 严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符, 这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没 有空隙存在。
- 魔术
- 常量池
- 访问标志
- 类索引、父类索引与接口索引集合
- 字段表集合
- 方法表集合
- 属性表结合
javap -verbose HelloWorld.class
----------------------------------------------------------------------------------------------------
6.14
1) 加载和存储指令
- 加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输
- 将局部变量表加载到操作数栈
- 将一个数值从操作数栈存储到局部变量表
- 将一个常量加载到操作数栈
- 扩充局部变量表的访问索引的指令:wide
2) 类型转换指令
- 宽化
- 窄化
3) 对象的创建与访问指令
- 创建实例的指令
- 创建数组的指令
- 访问类字段
- 把数组元素加载到操作栈的指令
- 将操作数栈的值存储到数组元素
- 取数组长度的指令
- 检查实例类型的指令
4) 操作数栈的管理指令
5) 控制转移指令
6) 方法调用指令
- invokevirtual 调用对象实例方法
- invokeinterface 调用接口方法
- invokespecial 调用一些需要特殊处理的实例方法
- invokestatic 调用类方法
7) 返回指令
8) 异常处理指令
9) 同步指令
10) 类加载机制
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校 验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
11) 类的加载时机
12) slot
------------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------
《深入理解Java虚拟机》
1. 走进Java
JDK:Java程序设计语言、Java虚拟机、Java API类库
平台:
- Java Card 支持一些Java小程序运行在小内存设备(如智能卡)上得平台
- Java ME(Micro Edition) 移动终端(如手机)
- Java SE(Standard Edition) 桌面级应用
- Java EE(Enterprise Edition) 多层架构企业应用
Sun Hotspot VM
OpenJDK
2. Java内存区域与内存溢出异常
-----------------------------------------------------------------------------------------------------------
程序计数器:
- 一块较小得内存空间,它可以看作是当前线程所执行得字节码的行号指示器
- 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成
- 由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令
- 为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域 为“线程私有”的内存。
虚拟机栈:
- 是java方法执行得内存模型,每个方法在执行得同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息
- 局部变量表存放了编译期可知的各种基本数据类型(boolean、 byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和 returnAddress类型(指向了一条字节码指令的地址)。
- 局部变量空间(Slot)
本地方法栈
Java堆
- Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的 对象实例都在这里分配内存。
- Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”
- 从内存回 收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆 中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。
- Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。
方法区:
- 是各个线程共享得内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据
- 根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时, 将抛出OutOfMemoryError异常
---------------------------------------------------------------------------------------------------
直接内存:
- 直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分, 也不是Java虚拟机规范中定义的内存区域。
对象的创建:
- 在语言层面上,创建对象(例如克隆、反序列 化)通常仅仅是一个new关键字而已
- 虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
- 为对象分配空间的任务等同于把一块确定大小的内存从Java堆 中划分出来。
- 假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”(Bump the Pointer)
- 如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称 为“空闲列表”
- 选择哪种分配方式由Java堆是否规整决 定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
- 对象在虚拟机中是十分频繁的行为,在并发情况下也并不是线程安全的。一种是对分配内存空间的动作进行 同步处理——实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;另一种是把内存分配的动作按照线程划分在不同的空间之中 进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配 缓冲(Thread Local Allocation Buffer,TLAB)
对象的内存布局
- 对象在内存中存储的布局可以分为3块区域: 对象头(Header)、实例数据(Instance Data)和对齐填充 (Padding)
- 对象头包括两部分信息,第一部分用于存储对象 自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标 志、线程持有的锁、偏向线程ID、偏向时间戳等。
- 对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
- 实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。从分配策略中可以看出,相同宽度的字段总是被分配到一起。在满足这个前提条件的情况 下,在父类中定义的变量会出现在子类之前
- 对齐填充并不是必然存在的,也没有特别的含义,它仅仅 起着占位符的作用。
对象的访问定位:
- 目前主流的访问方式有使用句柄和直接指针两种。
使用句柄访问的话,那么Java堆中将会划分出一块内存来作为 句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象 实例数据与类型数据各自的具体地址信息
使用直接指针访问,那么Java堆对象的布局中就必须考虑如何 放置访问类型数据的相关信息,而reference中存储的直接就是对象地址
- 使用句柄来访问的最大好处就是 reference中存储的是稳定的句柄地址
- 使用直接指针访问方式的最大好处就是速度更快
----------------------------------------------------------------------------------------------------
3. 垃圾收集器与内存分配策略
- 哪些内存需要回收
- 什么时候回收
- 如何回收
Java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问题,因为方法结束 或者线程结束时,内存自然就跟随着回收了
而Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集 器所关注的是这部分内存
---------------------------------------------------------------------------------
引用计数算法:给对象中添加一个 引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效 时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。缺点:当两个对象已经不可能再被访问,但是它们因为相互引用着对方,导致引用计数都不为0,则GC无法回收。
可达性分析算法:这个算法的基本思路就是通过一系列的称 为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走 过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有 任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可 达)时,则证明此对象是不可用的。
GC Roots对象:
- 虚拟机栈中引用的对象
- 方法取中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中引用的对象
要真正宣告一个对象死亡,至少要经 历两次标记过程:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件 是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize() 方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况 都视为“没有必要执行”。
回收方法区:在方法区中进行垃圾收集的“性价比”一般比较低:在堆中,尤其是在新生代中,常规应用进行一次垃圾收集一般可以回收70% ~95%的空间,而永久代的垃圾收集效率远低于此;永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。
-----------------------------------------------------------------------
垃圾收集算法:
标记-清除算法:算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象
它的主要不足有两个:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连
续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾 收集动作
复制算法:它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效
IBM公司的 专门研究表明,新生代中的对象98%是“朝生夕死”的,所以并不需要按 照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和 两块较小的Survivor空间,每次使用Eden和其中一块Survivor[1]。当回收 时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块 Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间
标记-整理算法:根据老年代的特点,有人提出了另外一种“标记-整理”(MarkCompact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动, 然后直接清理掉端边界以外的内存
分代收集算法:一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每
次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中 因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
----------------------------------------------------------------------------------------------------------
垃圾收集器
Serial:Serial收集器是最基本、发展历史最悠久的收集器,曾经(在JDK 1.3.1之前)是虚拟机新生代收集的唯一选择。这 个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明 它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结 束
ParNew收集器:ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条 线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参 数、收集算法、Stop The World、对象分配规 则、回收策略等都与Serial收集器完全一样,在实现上,这两种收集器 也共用了相当多的代码
- 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
- 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集 程序运行于另一个CPU上。
Parallel Scavenge收集器:是一个新生代收集器,它也是使用复制算法 的收集器,又是并行的多线程收集器
Serial Old:是Serial收集器的老年代版本,它同样是一个单线程收集 器,使用“标记-整理”算法。
Parallel Old:是Parallel Scavenge收集器的老年代版本,使用多线程 和“标记-整理”算法
CMS:(Concurrent Mark Sweep)是一种以获取最短回收停顿 时间为目标的收集器 从Mark Sweep看使用标记-清除算法
- 初始标记
- 并发标记
- 重新标记
- 并发清除
初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速 度很快,并发标记阶段就是进行GC RootsTracing的过程,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
G1:
- G1是一款面向服务端应用的垃圾收集器
- 并行与并发
- 分代收集
- 空间整合
- 可预测的停顿
G1运作
- 初始标记:初始标记阶段仅仅只是标记一下 GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这阶段需要停顿线程,但耗时很短
- 并发标记:从 GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行
- 最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记 录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面, 最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set
中,这阶段需要停顿线程,但是可并行执行 - 筛选标记:首先 对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿 时间来制定回收计划
GC日志:
- “33.125:”和“100.667:”代表了GC发生的时间,这个 数字的含义是从Java虚拟机启动以来经过的秒数。
- “[GC”和“[Full GC”说明了这次垃圾收集的停顿类 型,而不是用来区分新生代GC还是老年代GC的
- “[DefNew”、“[Tenured”、“[Perm”表示GC发生的区域,这 里显示的区域名称与使用的GC收集器是密切相关的,例如上面样例所 使用的Serial收集器中的新生代名为“Default New Generation”,所以显示 的是“[DefNew”。如果是ParNew收集器,新生代名称就会变 为“[ParNew”,意为“Parallel New Generation”。如果采用Parallel Scavenge收集器,那它配套的新生代称为“PSYoungGen”,老年代和永久代同理,名称也是由收集器决定的
- “3324K->152K(3712K)”含义是“GC前该内存 区域已使用容量->GC后该内存区域已使用容量(该内存区域总容 量)”。而在方括号之外的“3324K->152K(11904K)”表示“GC前Java 堆已使用容量->GC后Java堆已使用容量(Java堆总容量)”
- “0.0025925 secs”表示该内存区域GC所占用的时间,单位 是秒
------------------------------------------------------------------------------------------------------------------------
内存分配与回收策略
- 内存分配,往大方向讲,就是在堆上分配。对象主要分配在 新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在 TLAB上分配。少数情况下也可能会直接分配在老年代中,分配的规则并不是百分之百固定的,其细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数的设置。
- 对象优先再Eden分配 当Eden区没有足够空间分配时,虚拟机将发起一次Minor GC
- 大对象直接进入老年代
- 长期存活的对象将进入老年代
- 动态对象年龄判断:虚拟机并不是永远地要求 对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在 Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到 MaxTenuringThreshold中要求的年龄
- 空间分配担保:Minor GC发生条件:虚拟机会先检查老年代最大可用的连续空间 是否大于新生代所有对象总空间;
----------------------------------------------------------------------------------------------------------------------------
4. 虚拟机性能监控与故障处理工具
5. 调优案例分析与实战
6. 虚拟机执行子系统
类文件结构:
- class文件时一组以8位字节为基础单位的二进制流,各个数据项目 严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符, 这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没 有空隙存在
- 无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个 字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描 述数字、索引引用、数量值或者按照UTF-8编码构成字符串值
- 一个字节2位,嗯。
1) 魔数
每个Class文件的头4个字节称为魔数(Magic Number),它的唯一 作用是确定这个文件是否为一个能被虚拟机接受的Class文件,0xCAFEBABE
2) 第5和第6个字 节是次版本号(Minor Version),第7和第8个字节是主版本号(Major Version)
3) 常量池
紧接着主次版本号之后的是常量池入口,常量池可以理解为Class文 件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类 型,也是占用Class文件空间最大的数据项目之一,同时它还是在Class文件中第一个出现的表类型数据项目
由于常量池中常量的数量是不固定的,所以在常量池的入口需要放 置一项u2类型的数据,代表常量池容量计数值
常量池容量(偏移地址: 0x00000008)为十六进制数0x0016,即十进制的22,这就代表常量池中 有21项常量,索引值范围为1~21
常量池中主要存放两大类常量:字面量(Literal)和符号引用 (Symbolic References)
4) 访问标志:在常量池结束之后,紧接着的两个字节代表访问标志 (access_flags),这个标志用于识别一些类或者接口层次的访问信息, 包括:这个Class是类还是接口;是否定义为public类型;是否定义为 abstract类型;如果是类的话,是否被声明为final等
5) 类索引、父类索引与接口索引集合:
6) 字段表集合:用于描述接口或者类中声明的变量。字段 (field)包括类级变量以及实例级变量,但不包括在方法内部声明的局 部变量
7) 属性表集合:
--------------------------------------------------------------------------------------------------------
字节码指令
Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的 数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需 参数(称为操作数,Operands)而构成
- 在Java虚拟机的指令集中,大多数的指令都包含了其操作所对应的 数据类型信息
- 加载指令
- 运算指令
- 类型转换指令
- 对象创建与访问指令
- 操作数栈管理指令
- 控制转移指令
- 方法调用和返回指令
- 异常处理指令
- 同步指令
---------------------------------------------------------------------------------------------------------
7. 虚拟机类加载机制
Class文件中描述的各种信息,最终都需要加载到虚拟机中之后才能运行和使用
类加载机制:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校 验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型
java语言中,类型的加载、连接和初始化过程都是再程序运行期间完成的
- 第一,在实际情况中,每个Class文件都有可 能代表着Java语言中的一个类或接口,后文中直接对“类”的描述都包括
了类和接口的可能性,而对于类和接口需要分开描述的场景会特别指 明; - 第二,与前面介绍Class文件格式时的约定一致,笔者本章所提到 的“Class文件”并非特指某个存在于具体磁盘中的文件,这里所说 的“Class文件”应当是一串二进制的字节流,无论以何种形式存在都可以。
类加载声明周期:
- 加载 load
- 验证 verification
- 准备 Preparation
- 解析 Resolution
- 初始化 Initialization
- 使用 Using
- 卸载 unloading
加载、验证、准备、初始化和卸载这5个阶段顺序是确定的,而解析阶段是不确定的
加载--Java虚拟机规范中并没有进行强制约束,这点可以交给虚拟机的具体实现来自由把 握。但是对于初始化阶段,虚拟机规范则是严格规定了有且只有5种情 况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之前
开始):
- 遇到new、getstatic、putstatic或invokestatic这4条字节码指令 时,如果类没有进行过初始化,则需要先触发其初始化。
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化
- 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main()方法的那个类),虚拟机会先初始化这个主类。
- 当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、 REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始
-----------------------------------------
加载-- 在加载阶段,虚拟机需要完成以下3件事:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
--------------------------------------------
验证:验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
----------------------------------------------
准备:准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。
- 这时候进行内存分配的仅包括类 变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象 实例化时随着对象一起分配在Java堆中。
- 这里所说的初始值“通 常情况”下是数据类型的零值
----------------------------------------------
解析:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
- 符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。
- 直接引用(Direct References):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。
-----------------------------------------------
初始化:到了初始化阶段,才真正开始执行类中定 义的Java程序代码(或者说是字节码)。
在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源
-----------------------------------------------------------------------------------
双亲委派模型:
从Java虚拟机的角度来讲,只存在两种不同的类加载器:
- 启 动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现 [1],是虚拟机自身的一部分;
- 另一种就是所有其他的类加载器,这些类 加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。
--------------
启动类加载器:
- 这个类 将器负责将存放在<JAVA_HOME>\lib目录中的,或者被Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加 载)类库加载到虚拟机内存中。
- 启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类 加载器,那直接使用null代替即可
扩展类加载器:
- 这个加载器由 sun.misc.Launcher $ExtClassLoader实现,它负责加载<JAVA_HOME> \lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
应用程序类加载器:这个类加载器由 sun.misc.Launcher $App-ClassLoader实现。
-----------------------------------------------------------------------------------------------------
8. 虚拟机字节码执行引擎
执行引擎是Java虚拟机最核心的组成部分之一。“虚拟机”是一个相 对于“物理机”的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器、硬件、指令集和操作系统层面上的,而虚拟机的执行引擎则是由自己实现的,因此可以自行制定指令集与执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令集格式。
运行时栈帧结构:
- 栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的 数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)[1]的栈元素
- 栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用开始至执行完成的过程,
都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
---------------------------------------------------------------------------------------------------------
9. 类加载及执行子系统的案例与实战
10. 早期(编译期)优化
11. 晚期(运行期)优化
12. java内存模型与线程
13. 线程安全与锁优化
---------------------------------------------------------------------------------------------------------
一头雾水。。。(小部分理解,大部分不理解)
---------------------------------------------------------------------------------------------------------
《Java虚拟机规范》
2. java虚拟结构
1)与java程序语言中的数据类型相似,java虚拟机可以操作的数据类型分为两类:
- 原始类型
- 引用类型
2)原始数据类型:数值类型(整数类型和浮点类型)、boolean类型、returnaddress类型
3)引用类型:类类型(执向动态创建的类实例)、数组类型(数组实例)和接口类型(实现某个接口的类实例或数组实例)。
4)运行时数据区
- pc寄存器(即程序计数器)
- 虚拟机栈--这个栈与线程同时创建,用于存储栈帧 由于除了栈帧的出栈和入栈之外。java虚拟机栈不会再受其它因素的影响,所以栈帧可以在堆中分配,java虚拟机栈所使用的内存不不需要保证是连续的。
- java堆
- 方法区
- 运行时常量池
- 本地方法栈
5)栈帧是用来存储数据和部分过程结果的数据结构,同时也用来处理动态链接、方法返回值和异常分派。栈帧随着方法调用而创建,随着方法结束而销毁。
-----------------------------------------------------------
6)高位对齐
3. Java虚拟机编译器
4. class文件格式
5. 加载、链接与初始化
6. Java虚拟机指令集
7. 操作码助记符
--------------------------------------------------------------------------------------------------
JVM好难。。。。。。。。。。。。