《深入理解Java虚拟机》 读书笔记

Java虚拟机运行时数据区

 

对象的创建

Java创建对象,在语言层面上使用new关键字。虚拟机遇到new关键字时,会检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有,那就必须先执行类加载过程。类加载通过之后,虚拟机将会为新生对象分配内存。对象所需的内存在类加载完成后就能完全确定。分配内存的方法有“指针碰撞”和“空闲列表”两种方式,如果Java堆是规整的,则采用前者;否则,采用后者。Java堆是否规则和虚拟机有关。

OOM

在虚拟机中,能够发生OOM的有:虚拟机栈,本地方法栈,Java堆,方法区,运行时常量。分析OOM使用eclipse memory analyzer插件或者JProfiler工具。添加jvm参数(-XX:+PrintGCDetails)可以打印出GC的详细日志,-Xloggc:gc.log 日志文件的输出路径。

Java堆

几乎所有的对象的视力都在这里分配内存。通过参数-Xmx(JVM最大可用内存)和-Xms(JVM初始可用内存)控制对的大小。如果在堆中没有内存完成实例分配并且堆无法扩展,就会OOM。栈-Xss设置栈的容量大小。HotSpot不区分本地方法栈和虚拟机栈。如果虚拟机在申请栈扩展时,没有足够的空间,则会OOM。

垃圾收集器与内存分配策略

判断对象是否存活,两种方式:引用计数法和可达性分析。

引用计数法,不能解决对象相互引用的情况,所以主流虚拟机都采用的是可达性分析。

可达性分析采用采用GCROOTS策略,可作为GCROOTS的有:

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • Native方法中引用的对象

一、不同虚拟机对比表

虚拟机 新生代 老年代 垃圾算法 备注
Serial   复制算法  
ParNew   复制算法  
Parallel Scavenge   复制算法  
Serial Old   标记-整理算法  
Parallel Old   标记-整理算法  
CMS   标记-清除算法  
G1   综合算法 目前最先进的垃圾回收器

 

二、理解GC日志

下面是一次GC产生的日志(jvm参数-XX:+PrintGCDetails):

从日志中可以看到,System.gc()表示程序调用了System.gc()方法触发了垃圾回收,[PSYoungGen:3932k->592k(76288k)]中PSYoungGen代表了GC发生的区域,这个名称和GC收集器密切相关:如果显示的是[DefNew,则表明是Serial收集器的新生代;如果是ParNew则表明是ParNew收集器的新生代;我们这里是PSYoungGen,则表明是Parallel Scavenge收集器的新生代。再看后面是数字,3932k->592k(76288k)代表的是:GC前该内存已经使用的容量->GC后该内存区域使用的容量(该内存区域总容量)。3932K->600K(251392K)表示GC前Java堆已经使用的容量->GC后Java堆已经使用的容量(Java堆总容量)。后面的时间表示GC所花费的时间,单位s。 

三、内存分配与回收策略

1、对象优先分配在Eden区

大多数情况下,对象首先会被分配到Eden区,当Eden区满了,会触发一次Minor GC。

大对象直接进入老年区

所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组(笔者列出的例子中的byte[]数组就是典型的大对象)。虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配。这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制(复习一下:新生代采用复制算法收集内存)。

2、长期存活的对象进入老年区

对象在Survivor区中每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就将会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。

3、动态对象年龄的判断

虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

4、空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的,如果担保失败则会进行一次Full GC;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。

虚拟机性能监控与故障处理工具

jps:虚拟机进程状况工具

jps格式:jps [options] [hostid]

功能:列出正在执行的虚拟机进程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称以及这些进程的本地虚拟机唯一ID(Local Virtual Machine Identifier,LVMID)。

选项 作用
-q 只输出LVMID,省略主类的名称
-m 输出虚拟机进程启动时传给主类main()的参数
-l 输出主类的全名,如果主类执行的是jar,则输出jar的路径
-v 输出虚拟机进程启动时传给jvm的参数

jstat:虚拟机统计信息监视工具

功能:它可以显示本地或者远程[1]虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。

命令格式:jstat [option vmid [ interval [s|ms] [count] ] ],参数interval和count代表查询间隔和次数。

其中,如果进程是本地虚拟机进程,则vmid与lvmid一致;如果是远程虚拟机进程,则vmid格式为:[protocol:][//]lvmid[@hostname[:port]/servername]

举例:

S0代表Survivor0;S1代表Survivor1;E代表Eden区;O代表Old区;M代表Metaspace元数据区域(JDK1.8用Metaspace代替了1.7以前的PermGen永久代);CCS表示的是NoKlass Metaspace的使用率也就是CCSU,/CCSC算出来的,具体可参看这里,总之在1.7之前,M所在的位置是P,表示永久代使用的比例;YGC代表程序运行以来共发生Minor GC;YGCT表示其所用的时间;FGC代表程序运行以来共发生Full GC;FGCT表示其所用的时间;GCT表示GC总共花费了所长时间。

jinfo:Java配置信息工具

格式:jinfo [option] pid

作用:实时的查看和调整java虚拟机各项参数。

jmap:Java内存映像工具

格式:jmap [option] vmid

作用:用于生成堆转储快照(一般称为heapdump或dump文件)。

选项 作用
-dump 生成Java堆转储快照。格式:-dump:[live,] format -b,file=<filename>,其中live字参数说明是否只dump存活的对象。
-heap 显示Java堆详细信息,只在Linux/Solaris平台下有效
-histo 显示Java堆中对象统计信息
-F 当虚拟机对-dump选项没有响应时,该参数可以强制生成dump快照。只在Linux/Solaris平台下有效

jhat:虚拟机堆转储快照分析工具

作用:配合jmap命令,分析dump文件。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果可以在浏览器中查看。

命令:jhat dumpfile

一般使用专业的工具进行分析,例如:VisualVM,Eclipse Memory Analyzer,IBM HeapAnalyzer等。

jstack:Java堆栈跟踪工具

格式:jstack [option] vmid

作用:用于生成虚拟机当前时刻的线程快照,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些什么事情,或者等待着什么资源。

类文件结构

1. 无关性的基石

任何语言,不局限于Java,都可以在虚拟机上运行。Java虚拟机不care .class文件来自何种语言。

2. Class类文件结构(这部分看不懂,需要汇编知识,后续补充)

Class文件是一组以8位字节为基��单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符

根据Java虚拟机规范规定,Class文件中采用类似C语言结构体中的伪结构体来存储数据,这种结构体中只存在两种数据结构:无符号数和表。

  • 无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
  • 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“_info”结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表

2.1 魔数和Class文件的版本

每个Class文件的头4个字节被称为魔数,它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。Class文件的魔数为:0xCAFFEEBABE。紧接着魔数的4个字节存储的是Class文件的版本号:第5和第6个字节是次版本号(Minor Version),第7和第8个字节是主版本号(Major Version)。Java的版本号是从45开始的,JDK 1.1之后的每个JDK大版本发布主版本号向上加1(JDK 1.0~1.1使用了45.0~45.3的版本号),高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件,即使文件格式并未发生任何变化,虚拟机也必须拒绝执行超过其版本号的Class文件。 

2.2 常量池

紧接着主版本号后面的是常量池入口。

猜你喜欢

转载自www.linuxidc.com/Linux/2017-06/144523.htm