Java虚拟机学习笔记

Java虚拟机学习笔记

视屏网址:

https://www.365yg.com/a6501513115182563854#mid=7002701897

一、走进JVM

    JVM一般由器、方法区、java堆、java拟栈PC程序数器、本地方法行引擎等成。

二、运行时数据区域

2.1 概念:

2.1.1程序计数器

程序计数器是一块较小的内存空间,线程私有,主要用于指示当前字节码执行的行号。如果线程正在执行一个java方法,这个计数器记录的是正在执行的虚拟机字节码的地址,如果现在正在执行Native方法,此时计数器值为空(undefined)。此区域是唯一一个java虚拟机规范中没有OutOfMemoryError的区域。

2.1.2 Java虚拟机栈

(1)栈说明:

与程序计数器一样,java虚拟机栈也是线程私有的,他的生命  周期和线程相同。虚拟机栈描述的是java的内存模型:每个方法被执行时都会创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息,方法的执行过程就是入栈和出栈的过程。

(2)局部变量表:

存放编译时可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用和returnAddress类型(指向一条字节码指令的地址)。

注意:局部变量表所需要的内存空间在编译时期就完全确定了,在方法运行时期不再改变。

(3)异常情况:

         在java虚拟规范中,对这个区域规定了两种异常状态。如果线程请求的栈深度大于虚拟机允许的深度,将抛出StackOverflowError如果虚拟机栈可以动态扩张,当扩张时无法申请到足够的内存时会抛出OutOfMemoryError异常。

2.1.3本地方法栈

本地方法栈与虚拟机栈所发挥的作用非常类似。其区别在于虚拟机栈为虚拟机执行java方法服务,而本地方法栈为虚拟机使用navtive方法服务。

2.1.4 Java堆

Java堆是Java虚拟机所管辖的内存最大的一块。Java堆是被所有线程共享的。该区域唯一的目的就是存放对象实例。Java堆是垃圾回收管理的主要区域,所以也叫GC堆。因此java堆可细分为:新生代和老年代(Eden空间、From Survivor空间、To Survivor空间等)

2.1.5方法区

方法区主要用于存储被java虚拟机加载的类信息、常量、静态变量、即编译器编译后的代码等数据。Java虚拟机对这个范围的限制非常宽松,可以选择不实现垃圾回收。但是并非数据进入了方法区就如永久代的名字一样,永久存在。这个区域的回收主要是针对常量池的回收和对类的卸载。

2.2 对象访问

不同虚拟机实现对象的访问方式不同,主流的有两种:使用句柄访问和直接指针访问。

  1. 使用句柄访问:

       java堆中会划分出一块内存来作为句柄池,java栈(reference)中存储的的是对象的句柄地址,而句柄中包含来对象实例数据和类型数据各自的具体地址信息。

  1. 直接指针访问

若使用直接访问方式,java堆对象的布局中就必须考虑如何放置访问类型数据和相关信息,reference中直接存在的是对象的地址。

 

句柄访问和直接指针访问的优缺点:

        句柄访问:使用句柄访问的最大优点是reference中存储的是稳定的句柄地址,在对象被移动时(垃圾收集时移动对象是非常普遍的行为)只会改变句柄中的实例数据指针,而reference本身不需要改变。

       直接指针访问:直接指针访问最大的特点就是速度快,它节省来指针定位的时间。由于对象的访问在java中非常频繁,因此该类开销积少成多也是也是非常客观的成本,现在sun HotSpot就是使用的第二种方式。

 

2.3  实战:OutOfMemoryError异常

2.3.1 java堆内存溢出

        获取heap dump的方式:

  1. 设置jvm参数:

 -XX:+HeapDumpOnOutOfMemoryError :jvm会在发生内存泄露时抓拍到当时的内存状态。

-XX:+HeapDumpOnCtrlBreak:非奔溃时获取

  1. JMap,JConsole工具获取

jmap -dump:format=b,file=<dumpfile> <pid>

解释:format=b-->指定格式为二进制;file=<dumpfile>-->指定文件名称,自定义;<pid> -->进程id

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

由于hotspot虚拟机中并不区分虚拟机栈和本地方法栈,因此对于 hotspot来说,-Xoss参数(设置本地方法栈大小),虽然存在,但实际是无效的,栈容量只有-Xss参数设定。关于虚拟机栈和本地方法栈,在java虚拟机规范中描述了两种异常:

  1. 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StactOverflowError异常
  2. 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutofMemoryError异常。

2.3.3 运行时常量池溢出

2.3.4 方法区溢出

2.3.5 本机直接内存溢出

 

 

 

三、垃圾收集器余内存分配策略

3.1 概述

完成GC需要考虑的三件事:

  1. 哪些内存需要回收?
  2. 是什么时候回收
  3. 如何回收?

3.2 对象已死?

堆中几乎存放了所有的java对象实例,垃圾收集器在对堆进行回收前,需要考虑哪些对象还活着,哪些对象已经死去。

3.2.1 引用计数法

3.2.2 根搜索算法

3.3 垃圾收集算法

3.3.1 标记-清除算法

3.3.2  复制算法

3.3.3  标记-整理算法

3.3.4  分代收集算法

 

 

3.4  垃圾收集器

3.4.1 Serial 收集器

3.4.2  ParNew 收集器

3.4.3  Parallel Scavenge 收集器

3.4.4 Serial Old 收集器

3.4.5 Parallel Old 收集器

3.4.6 CMS 收集器

3.4.7 G1 收集器

3.5 内存分配与回收策略

3.5.1 对象优先在Eden分配

 Minor GC 与Full GC

        1. 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为java大多对象都具备朝生夕死的特征,所以Minor GC非常频繁,回收速度快。
        2. 老年代GC(Full GC):指发生在老年代的GC,出现了Major GC,经常伴随至少一次Minor GC(但非绝对),Major GC的速度一般比Minor GC 慢10倍。

3.5.2  大对象直接进入老年代

 

3.5.3 长期存活的对象将进入老年代

3.5.4 动态对象年龄判断

 

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

4.1 JDK 常用工具

4.1.1 jps : 虚拟机进程状况工具

jps可以列出所有正在允许的虚拟机进程,并显示虚拟机执行的主类的名称,以及这些进程的本地虚拟机唯一id。

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

jstat用于监视虚拟机各运行状态

4.1.3 jmap : java内存映象工具

 

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

jhat和jmap搭配使用,来分析jmap生成的堆转储快照,jhat内置来一个微型的HTTP/HTML服务器,生成dump文件的分析结构后可以用浏览器打开查看,但是一般不用,因为可以使用更专业的Eclipse Memory Analyzer 、IBM HeapAnalyzer等工具进行分析。

4.1.5 jstatck : java堆栈跟踪工具

jstack 命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或javacore文件)。线程快照就是当前java虚拟机正在执行的方法堆栈的集合。生成线程快照的目的主要是用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源长时间等待等。

4.2 JDK 可视化工具

4.2.1 JConsole : java监视与管理控制台

4.2.2 VisualVM : 多合一故障处理工具

五、虚拟机类加载机制

5.1 类加载的时机

5.1.1 类的生命周期

类从被加载到虚拟机内存到卸载为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用、卸载七个阶段。

5.1.1 什么情况下对类进行初始化

  1. 遇到new、getstatic、putstatic、invokestatic 四个指令时,如果类没有初始化,需要对类进行初始化。
  2. 使用java.lang.reflect包的方法对类进行反射调用时,如果类没有初始化,需要对类进行初始化。
  3. 当初始化一个类时,若其父类没有初始化,需要对父类进行初始化
  4. 当虚拟机启动时,用户需要指定一个执行的主类,虚拟机需要先初始化这个主类

注意:除了这四种之外,其他的都不会触发初始化,称为被动应引用。如:

  1. 通过子类引用父类的静态字段,不会导致子类初始化
  2. 通过数组定义来引用类,不会触发此类的初始化
  3. 常量在编译阶段会存入调用类的常量池中,本质上没有引用到定义常量的类,因此不会触发类的初始化。

5.2 类加载过程

5.2.1 加载

类加载阶段,虚拟机需要做以下三件事:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据接口
  3. 在java堆中生成一个代表这个类的java.lang.class对象,作为方法区这些数据的入口

5.2.2 验证

验证是连接阶段的第一步,目的是确保class文件字节流中包含的信息符合虚拟机的要求。主要包括文件格式验证、元数据验证、字节码验证、符号引用验证等。

5.2.3 准备

准备阶段主要是为类变量分配内存并设置类变量初始值的阶段。这些内存都将在方法区中进行分配。

这阶段有两个容易混淆点:

  1. 内存分配仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配到堆内存中。通常说的初始值为数据类型的0值:如 public static int value =123 ;那么变量在准备阶段的出事值为0。
  2. 若字段属性存在ConstantValue属性,那么初始化值为属性所指定的值。如public static final int value = 123; 准备阶段的值为123。

5.2.4 解析

解析阶段是将虚拟机常量池内的符号引用替换成直接引用的过程;解析主要针对类或接口、字段、类方法、接口方法四类符号引用进行

5.2.5 初始化

在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,主要是根据程序员通过程序制定的主要计划初始化类变量和其他资源。即初始化阶段是执行类构造器<clinit>()方法的过程。<clinit>()方法是由编译器自动收集类中的所有类变量的赋值和静态代码块中语句合并参数的。

 

5.3 类加载器

5.3.1 双亲委派模型

          站在java虚拟机角度讲,只存在两种不同的类加载器:启动类加载器,这个类加载器是使用c++语言实现的,是虚拟机自身的一部分;另一个是所有其他类加载器。细分可以分为以下三种:

  1. 启动类加载器

这个类加载器负责加载存放到<JAVA_HOME>\lib目录中的,或者-Xbootclasspath参数指定路径下的类库,启动类加载器无法被java程序直接使用

  1. 扩展类加载器

这个加载器负责加载<JAVA_HOME>\lib\ext目录下的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

  1. 应用程序类加载器(即系统类加载器)

负责加载用户类路径classpath上所指定的类库,开发者可以直接使用这个类加载器。若应用程序中没有自定义类加载器,一般情况下这个就是程序中默认的类加载器。

双亲委派模型的执行过程:

如果一个类加载器收到了类加载请求,它首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器去完成,每个层次的类加载器都如此,因此所有的类加载请求最终都应该传送给顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会自己尝试加载。

5.3.2 破坏双亲委托模型

  1. jdk1.2之前的版本,面对已经存在的用户自定义类加载器的实现代码。java设计者们需要做一些兼容
  2. 解决基础类调用用户自身代码等问题
  3. 解决代码热替换、热部署问题(机器不需要重启)

六、java内存模型

 

5.3.3 Tomcat类加载器

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值