深入理解Java虚拟机

深入学习Java虚拟机总结

以Java虚拟机为平台进行程序设计的开发人员,很有必要对虚拟机的原理及工作机制进行深入的了解,该篇是学习周志明著深入理解Java虚拟机第2版的总结,主要分为以下几个方面:

  • Java内存区域与内存溢出异常
  • 垃圾收集器与内存分配策略
  • 虚拟机性能监控与故障处理工具
  • 类文件结构
  • 虚拟机类加载机制

1、Java内存区域与内存溢出异常

必要性:Java虚拟机自己运行着一套垃圾回收处理机制,出现内存泄露或溢出异常情况时,需要了解其根本原因进行合理解决。因此需要首先了解JVM的各个区域。
运行一个Java程序时会把虚拟机内存划分为若干区域,各自有创建和销毁时间。
Java虚拟机运行时数据区

红色字体所代表的区域为线程共享的数据区;白色字体代表线程隔离的数据区

1.1程序计数器

可简单理解为当前线程所执行字节码的行号指示器,字节码解释器通过改变这个数值来选取需要执行的下一条字节码,各线程间计数器互不影响,独立存储。

1.2Java虚拟机栈

  • 线程私有,生命周期通线程
  • 每个Java方法在执行时都会先创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息
  • 每个方法从调用—>执行完成对应一个栈帧在虚拟机中入栈—>出栈
    Java虚拟机栈
  • 局部变量表存放:基本数据类型、对象引用类型(reference)
  • 局部变量表所需要的空间在编译时期完成分配
  • 线程请求深度大于虚拟机所允许最大深度会抛出StackOverFlowError异常;虚拟机栈扩展时无法申请到足够内存会OOM

1.3本地方法栈

大体上与虚拟机栈类似,区别在于服务对象不同,虚拟机栈是为虚拟机执行Java方法服务,本地方法栈是为虚拟机执行Native方法服务

1.4Java堆

  • 虚拟机启动时创建,线程共享的一块内存区域
  • 唯一目的就是存放对象实例,几乎所有的对象实例,几乎所有的对象实例都在这里分配内存。
  • 垃圾收集器的主要工作区域
  • 从内存回收角度来看,由于现在收集器基本都采用分代手机算法,所以这个内存又可以细分为:新生代和老年代。

1.5方法区

  • 线程共享的内存区域
  • 用于存储已经被虚拟机加载的类信息、常量、静态变量、及时编译器编译后的代码等数据
  • 内存回收主要针对该区域的常量池和类型卸载
  • 运行时常量池是方法区的一部分,class文件中有类的版本、字段、方法、接口、常量池等信息,用于存放编译期生成的各种字面量和符号引用

2、虚拟机中对象探索

了解了Java虚拟机的运行时数据区,大概知道了虚拟机内存的概况,知道了每个区域存放了些什么之后,进一步想知道这些虚拟机内存中的数据的细节:如何创建、如何布局、如何访问。以HotSpot虚拟机Java堆中对象的分配、布局、访问为例进行研究。

2.1对象的创建

当虚拟机收到一条new指令,首先,检查这个指令的参数在常量池中能否定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没定位到后者没有被加载、解析和初始化过,那必须执行相应的类加载过程。然后在堆中为对象分配内存;紧接着,虚拟机将为对象分配到的内存空间初始化零值(对象头除外);再然后,虚拟机对对象进行设置,说明该对象是哪个类的实例、如何能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,存储于对象头之中;最后,调用对象的构造函数进行初始化。
JAVA虚拟机中对象创建过程

2.2对象的内存布局

对象在内存中的布局可以分为3个部分:对象头、实例数据、对其填充。

  • 对象头:包含两个部分。第一个部分,存储对象的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等;另一部分是类型指针,对象指向它的类元数据指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,如果对象是一个数组,对象头中必须有一块用于记录数组长度的数据。
  • 实例数据:对象中主要的存储信息,例如各种类型字段内容。
  • 对其填充:没特殊含义,不是必然存在的。由于对象的起始地址必须是8字节的整数倍,当对象实例数据部分没有对齐,也就是说总体不是8字节的整数倍的时候,需要通过对其填充来补全。

2.3对象的访问定位

创建对象的目的就是为了使用对象,Java程序需要通过栈上的reference数据来操作堆上的具体对象。根据虚拟机的实现方式不同主流的访问方式为:使用句柄和直接指针两种。

  • 引用:JDK1.2之前定义为,如果reference类型的数据中存储的数值代表的是另一块内存的起始地址就称这块内存代表着一个引用;JDK1.2以后分为强引用,软引用,弱引用,虚引用。
  • reference类型:引用类型,例如:类class、接口interface、数组array
  • 句柄访问:如果使用这种方式访问对象,Java堆终将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,句柄中包含对象实例数据与类型数据各自的具体地址信息。
    通过句柄访问对象
  • 直接指针访问:reference中存储的直接就是对象的指针。
    通过直接指针的方式访问对象

    3.关于OutOfMemoryError异常

    JVM虚拟机中除了程序计数器外,其他运行时区域都有发生OOM的可能。根据异常信息快速判断出是那个区域的内存溢出,分析什么样的代码可能导致这些区域的溢出,以及如何处理是必备技能。

  • JAVA堆OOM:当Java程序频繁的大量创建对象,并且不再使用的对象无法被垃圾收集器回收。堆中没有内存来完成实力分配,并且无法在扩展时产生OOM;异常堆栈信息“Java.lang.OutOfMemoryError”进一步提示“Java heap space”。首先Dump出对转储快照,通过MAT等工具分析泄露对象到GC Root通过怎样的路径相关联,判断是内存泄露还是溢出以及泄露代码位置。

  • 虚拟机栈和本地方法栈:扩展时无法申请到足够的内存。异常信息标志:”unable to create new native thread”
  • 方法区和运行时常量池:无法满足内存分配需求。异常信息标志:“PermGen space”
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

路漫-其修远兮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值