深入理解java虚拟机

简介: 说在前面的话 link 是一本jvm入门经典,推荐所有java工程师阅读,并应该多读,不同阶段读. 这篇博客就是为了总结本人从该书中的领悟. 运行时的数据分区 程序计数器:线程执行的字节码行号. 虚拟机栈:生命周期和线程相同,描述的是java方法执行的内存模型, 每个方法执行的同时都会创建一个栈帧(stack frame).存放局部变量、操作数栈、动态链接、方法出口等信息. 堆:存放对象实例.随着JIT编译器、逃逸分析技术,栈上分配、标量替换导致不一定所有的对象都在堆上. 方法区:虚拟机加载的类信息、常量、静态变量、JIT编译的代码。

说在前面的话
<深入理解java虚拟机> link 是一本jvm入门经典,推荐所有java工程师阅读,并应该多读,不同阶段读. 这篇博客就是为了总结本人从该书中的领悟.

运行时的数据分区


image

  • 程序计数器:线程执行的字节码行号.
  • 虚拟机栈:生命周期和线程相同,描述的是java方法执行的内存模型, 每个方法执行的同时都会创建一个栈帧(stack frame).存放局部变量、操作数栈、动态链接、方法出口等信息.
  • 堆:存放对象实例.随着JIT编译器、逃逸分析技术,栈上分配、标量替换导致不一定所有的对象都在堆上.
  • 方法区:虚拟机加载的类信息、常量、静态变量、JIT编译的代码。堆的一个逻辑部分。hotspot使用永久代实现方法区,会受限于MaxPermSize
  • Direct Memory: DirectByteBuffer分配堆外内存.不受-Xmx参数控制.

java对象
java 对象的组成: 对象头(header)+实例数据(instance data)+对齐填充

  • 对象头:类的元数据、hash码、gc分代).
  • 实例数据:包括父类和自身定义的字段内容.
  • 对齐填充,保证8字节的整数倍.

对象的访问定位,通过栈上的reference操作堆.
image

内存/栈溢出

  • heap oom,分代(-Xmx -Xms:堆大小控制,Xmn:新生代大小,SurvivorRatio: survivor区大小).

     Memory Leak:内存无法被回收,GC Roots引用链.
     Memory overflow:调大内存,优化代码.
  • stack overflow(虚拟机栈和本地方法栈)

     栈深度支持到1000-2000,如果超过会导致stackoverflow.
     如果线程数量过多,则会报错out of memory(受限于一个进程可用内存-堆内存-方法区内存),每个线程需要分配一个栈容量,参数为-Xss.
  • 方法区和运行时常量池的溢出

     String.intern :常量池.
     方法区,除了已加载的类,还有许多动态生成类,如cglib、动态代理、osgi、jsp等。
  • 本机直接内存directMemory

     用反射操作unsafe.
    

GC

对象存活的判定算法:判断对象和GCROOT引用链是否可达来判断对象是否已死. GC root包括:

  • 虚拟机栈的本地变量表引用的对象.
  • 方法区中的静态变量引用的对象.
  • 方法区中常量池引用的对象.
  • 本地方法栈JNI引用的对象

引用的分类, 由强到弱:

标题1 标题2 标题3
强引用 Object o = new Object 永远不会回收被引用的对象
软引用 SoftReference 在内存溢出前被回收
弱引用 WeakReference 活到下一次gc.
虚引用 PhantomReference 无法通过引用获取,在被回收时收到通知

方法区的GC,回收效率低,效果差:

  • 无效的常量,如"abc"进入了方法区,但没有地方引用.
  • 无效的类信息, 如所有实例被回收/classLoad卸载.
  • 反射生成的类, 需要虚拟机具备类卸载功能.

垃圾收集算法:

  • 标记清除算法. 产生空间碎片,标记和清除的效率都比较低.
  • 标记复制算法. 内存可用空间降低, 新生代使用,由于大部分对象朝生夕死, 不需要1:1,一般是8:1:1.
  • 标记整理算法. 老生代使用.

垃圾收集器:
image

垃圾收集器 作用分代 特点 其他
Serial 新生代 简单,回收效率低,stop the word 用于client模式下
ParNew 新生代 并行回收,stop the word 用于server模式下.
Parallel Scavenge 新生代 并行回收,关注吞吐量 适合后台运算.
SerialOld 老年代 简单,回收效率低,stop the word 用于client模式下
ParallelOld 老年代 并行回收,关注吞吐量 适合后台运算.
CMS 老年代 多次标记,锁定stop the word时间, 关注响应时间 适合交互式的应用. 因为是标记清除算法,有内存碎片.
G1 独立完成新生代和老年代收集 分regin,按优先级回收,可预测的停顿 追求停顿时间短

GC日志
image

说明
33.125 从虚拟机启动到GC时经过的秒数
GC 表示本次GC并没有stop the world(并不是分代的区别)DefNew和收集器相关,Serial收集器定义的新生代
3324K->152K(3721K) GC前当前区域已使用的内存->GC后当前区域已使用的内存(该区域最大内存)
0.0025925secs GC所占用的时间,有些收集器会给出更详细的时间:

[Times:user=0.01 sys=0.00,real=0.02secs]
其他 | 外面的3324K->152K(11904K),..
表示当时java堆的情况

GC相关参数

参数 说明
NewRatio 新生代( Young ) 与老年代( Old ) 的比例,默认为1:2.
SurvivorRatio Eden区和survivor区的比例,默认8:1.
MaxTenuringThreshold 对象多少次minorGc后进入老年代.

虚拟机故障处理工具

名称 用途
jps 列出所有java进程
jmap 输出java内存信息
jstat java统计信息
jhat 分析java内存dump文件
jstack 输出java线程堆栈
jinfo 虚拟机配置信息
jvisualvm 可视化工具,Btrace织入调试代码.

最佳实践

  1. java进程内存不宜过大,64位jdk内存利用率低于32位.
  2. Runtime.getRuntime().exec() 是fork一个镜像java进程,去执行命令,效率低.
  3. 非堆内存包括direct memory,线程堆栈,socket buffer ,jvm本身占用.
  4. 集成慢系统,考虑用消息队列解耦.
  5. Map有效数据占比不高.

类文件结构

关于类的文件结构,只讲几个比较重要的概念

  1. 类的全限定名: java/lang/String.
  2. 方法描述: int typeOf(String a) 描述为(Ljava/lang/String a) I
  3. javap -verbose xx.class.

字节码指令

  • 基于操作数栈的指令.
  • 宽化处理, int->long,float,double ... 窄化处理,不会抛出运行时异常.
  • 异常由异常表处理,而不是指令.
  • 方法级的同步由管程(monitor)处理, 代码块由指令处理.

虚拟机类加载机制

image

  • 加载. 通过全限定名获取二进制流/将二进制流加载到虚拟机/生成class对象作为入口, 用户可以自定义加载方式(压缩包、网络、数据库、文件等)。
  • 验证. 验证字节流是否符合class文件规范,如果多次加载,可以关闭这个步骤.
  • 准备. 初始化类变量(0值).
  • 解析. 将符号引用转为直接引用.
  • 初始化. 执行类的()方法(()是实例方法), 静态块/类变量赋值等.
  • 使用.
  • 卸载.

类加载模型
双亲委派模型

  • 启动类加载器, javahome/lib,由c++实现.
  • 扩展类加载器, javahome/lib/ext.
  • 应用程序类加载器, 应用的classpath.
  • 先通过父加载器查找,如果查找不到则在本加载器内查找.
    1. spi上层调用下层(通过线程上下文加载器)都是破坏双亲委派模型.
      Osgi : 网状的模型.

虚拟机的字节码执行引擎

  • 基于栈桢, 包括操作数栈、局部变量表、动态链接、方法出口.
  • java是静态多分派(重载),动态单分派(重写).
  • 静态语言是变量类型编译器决定,符号引用中包含类型. 变量有类型.
  • tomcat 是正统的双亲委派模式. 默认 common/shared/webapp合三为lib.

    • common : tomcat/应用共享.
    • shared: 应用间共享

      • webapp: 应用内.

        • jsp: 动态部署.

      早期编译期优化

image

  • 分析与字节码生成中需要解语法糖(包括泛型、自动装箱/拆箱、可变参数、try resource、switch、遍历循环、内部类、断言、条件编译等).
  • java是低糖语言.

JMM
image

  • 原子性. 基础操作是原子的, 更大粒度的原子操作需要借助lock/unlock.
  • 可见性.volatile, 可以保证可见性, 在依赖运算判断/联合其他可变变量的情况以外,可以替代锁. final/synchronized也保证来可见性.
  • 有序性, 在多线程的情况下,对指令重排序,来加快运算速度。

    • volatile, 保证写优先与读.
  • 先行发生原则:程序有序/管程有序/Thread.start/Thread.interrupt/Thread终止/finalizer/volatile/传递性.

Java线程

  • 线程模型:内核态、用户态、混合模型.
  • 线程的调度方式:抢占式/协调式(不靠谱的优先级).
  • 线程的状态切换:image

线程安全和锁优化

  • 悲观锁(阻塞式), synchronize, ReentrantLock(多个条件绑定、公平和非公平锁、等待中断).
  • 乐观锁(非阻塞式), cas 由硬件支持.
  • 无锁模型: 可重入代码/ThreadLocal、生产者消费者.
  • 锁优化: 适应性自旋、锁消除、锁粗化、轻量级锁(先尝试一次cas)、偏向锁(同一个线程只做一次cas)
相关文章
|
8天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
4月前
|
存储 算法 安全
Java面试题:Java内存模型及相关知识点深度解析,Java虚拟机的内存结构及各部分作用,详解Java的垃圾回收机制,谈谈你对Java内存溢出(OutOfMemoryError)的理解?
Java面试题:Java内存模型及相关知识点深度解析,Java虚拟机的内存结构及各部分作用,详解Java的垃圾回收机制,谈谈你对Java内存溢出(OutOfMemoryError)的理解?
66 0
|
2月前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
98 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
3月前
|
Java
Java常见JVM虚拟机指令(47个)
Java常见JVM虚拟机指令(47个)
59 3
Java常见JVM虚拟机指令(47个)
|
3月前
|
Java 数据安全/隐私保护 Windows
【Azure Developer】使用Java代码启动Azure VM(虚拟机)
【Azure Developer】使用Java代码启动Azure VM(虚拟机)
|
4月前
|
监控 Oracle Java
(一)JVM成神路之初识虚拟机 - 探寻Java虚拟机的前世今生之秘
JVM(Java Virtual Machine)Java虚拟机的概念大家都不陌生,Java之所以可以做到“一次编译,到处运行”的跨平台性,其根本原因就在于JVM。JVM是建立在操作系统(OS)之上的,Java虚拟机屏蔽了开发人员与操作系统的直接接触,我们在通过Java编写程序时,只需要负责编写Java代码即可,关于具体的执行则会由JVM加载字节码后翻译成机械指令交给OS执行。
|
3月前
|
存储 Java API
【Azure Developer】通过Azure提供的Azue Java JDK 查询虚拟机的CPU使用率和内存使用率
【Azure Developer】通过Azure提供的Azue Java JDK 查询虚拟机的CPU使用率和内存使用率
|
4月前
|
存储 Ubuntu Java
【Linux】已解决:Ubuntu虚拟机安装Java/JDK
【Linux】已解决:Ubuntu虚拟机安装Java/JDK
120 1
|
4月前
|
监控 算法 Java
深入理解Java虚拟机:内存管理与性能优化
在Java的世界里,虚拟机(JVM)是幕后的守护者,它默默地支撑着每一个字节码的运行。本文将揭开JVM的神秘面纱,探讨其内存管理机制及如何通过调优提升应用性能。从堆内存的分配到垃圾回收的策略,再到实践中的性能监控与调优技巧,我们将一同走进JVM的内部世界,学习如何让我们的Java程序跑得更快、更稳。
|
4月前
|
Java
Java演进问题之单个虚拟机的最大线程数量一般会设置到200至400条如何解决
Java演进问题之单个虚拟机的最大线程数量一般会设置到200至400条如何解决