JVM

备注

  • 内存
    • 是非常重要的系统资源,是硬盘和cpu的中间仓库及桥梁,承载着操作系统和应用程序的实时运行。
    • JVM内存布局规定了java在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行。
    • 不同的JVM对于内存的划分方式和管理机制存在着部分差异。
  • JVM 定义了若干种程序运行期间会使用到的run-time data areas

Every Java application has a single instance of class Runtime that allows the application to interface with the environment in which the application is running. The current runtime can be obtained获得 from the getRuntime method.每个JVM只有一个Runtime实例。即为运行时环境

1
public class Runtime extends Object

线程

线程是一个程序里的运行单元。JVM允许一个应用有多个线程并发的执行。Hotspot JVM里,每个线程都与操作系统的本地线程直接映射。

  • 当一个java线程准备好执行以后,一个操作系统的本地线程也同时创建,java线程执行终止后本地线程也会回收
  • 操作系统负责所有线程的安排调度到任何一个可用的cpu上,一旦本地线程初始化成功,会调用java线程中的run()方法

线程出现异常

  • 捕获处理异常也相当于java线程正常终止
  • 未捕获处理异常,java线程肯定终止,此时操作系统还要判断一下是否要终止 JVM
    • deamon守护线程,如果JVM中只剩demonJVM可以退出
    • 非守护线程 当前线程为最后一个非守护线程则终止JVM

后台系统线程

hotspot JVM的后台系统线程,使用jconsole或者其他调试工具,能看到后台有许多线程在运行,不包括main方法的main线程以及所有这个main线程自己创建的线程

  • 虚拟机线程
    • 这种线程的操作是需要JVM达到安全点才会出现。
    • 这些操作必须在不同线程中发生的原因是他们都需要JVM达到安全点,这样heap area才不会变化。
    • 这种线程的执行类型包括STWstop-the-world垃圾收集线程栈收集线程挂起偏向锁撤销
  • 周期任务线程:这种线程是时间周期事件的体现比如中断,他们一般用于周期性操作的调度执行
  • GC线程:这种线程对在JVM里不同种类的垃圾收集行为提供了支持
  • 编译线程:这种线程在运行时会将字节码编译成本地代码
  • 信号调度线程:这种线程接收信号并发送给JVM,在它内部通过调用适当的方法进行处理

JVM Options

注意:-server 启动 server模式64位系统默认是Server模式,在server模式下才可以启用逃逸分析

1
2
3
4
5
6
7
8
9
mi :: ~ » java -version
openjdk version "17.0.7" 2023-04-18
OpenJDK Runtime Environment (build 17.0.7+7)
OpenJDK 64-Bit Server VM (build 17.0.7+7, mixed mode)

mi :: ~ » java -version
openjdk version "1.8.0_372"
OpenJDK Runtime Environment (build 1.8.0_372-b07)
OpenJDK 64-Bit Server VM (build 25.372-b07, mixed mode) 
  • -XX:+PrintFlagsInitial:查看所有参数的默认初始值
  • -XX:+PrintFlagsFinal:查看所有参数的最终值修改后的值
  • -Xms10M或者-XX:InitialHeapSize=10M 设置初始 heap size,只影响新生区养老区, 默认大小为物理机内存大小除以64。
  • -Xmx10M或者-XX:MaxHeapSize=10M 设置最大 heap size,只影响新生区养老区 默认大小为物理机内存大小除以4。
    • 通常将-Xms-Xmx设置为相同的值,目的是为了能够在Java垃圾回收机制清理完堆区后不需要重新分割计算堆区的大小从而提高性能
  • 设置Young区Old区的比例
    • -XX:NewRatio=2 ‘默认2,表示Young占1份,Old占2份’
    • -XX:NewRatio=4 ‘表示Young占1份,Old占4份’
  • -Xmn100M 设置 Young区 的大小一般不使用。当与-XX:NewRatio=2一起配置冲突时,以-Xmn设置的值来分配Young区大小,剩余的区域分给Old区
  • -XX:SurvivorRatio=8 设置Young区下的Eden区survivor0区、survivor1区的比例
    • 默认值8:1:1。由于自适应的内存分配策略,查看时可能是6:1:1,显式地设置为8查看时才是8:1:1
  • 开启 or 关闭自适应的内存分配策略
    • 开启 -XX:+UseAdaptiveSizePolicy
    • 关闭 -XX:-UseAdaptiveSizePolicy
  • -XX:MaxTenuringThreshold=15 设置Promotion晋升Old区阈值。默认值15
  • 开启 or 关闭 TLABThread Local Allocation Buffer
    • -XX:+UseTLAB 默认开启
    • -XX:-UseTLAB 关闭'
  • -XX:TLABSize=512k 设置TLAB空间大小。If this option is set to 0, then the JVM chooses the initial size automatically.
  • -XX:+PrintGCDetails 输出详细的GC处理日志
  • -XX:+PrintGC 输出GC简要信息
  • 逃逸分析JDK6_23后默认开启
    • -XX:+DoEscapeAnalysis 默认开启逃逸分析
    • -XX:-DoEscapeAnalysis 关闭逃逸分析
    • -XX:+PrintEscapeAnalysis 查看逃逸分析的筛选结果
  • 开启 or 关闭 标量替换
    • -XX:+EliminateAllocations 默认开启标量替换
    • -XX:-EliminateAllocations 关闭标量替换
  • Method area size 方法区
    • -XX:PermSize=20.75M JDK7及以前设置 PermanentGenerationSpace 初始值 默认20.75M
    • -XX:MaxPermSize=82M JDK7及以前设置 PermanentGenerationSpace 最大可分配空间。32位机器默认是64M,64位机器默认是82M
    • -XX:MetaspaceSize=21M JDK8及以后,设置元空间初始值,平台不同默认值不同,windows下默认约为21M\
    • -XX:MaxMetaspaceSize=-1 JDK8及以后,设置元空间最大可分配空间-1表示没有限制
  • -XX:HandlePromotionFailure=true 设置空间分配担保JDK6_24后过时

Native Method Stacks

Oracle 官方文档
  An implementation of the Java Virtual Machine may use conventional传统的 stacks, colloquially通俗地 called “C stacks,” to support native methodsmethods written in a language other than the Java programming language.Native method stacks may also be used by the implementation of an interpreter解释程序 for the Java Virtual Machine’s instruction(计算机的)指令 set in a language such as C. Java Virtual Machine implementations that cannot load native methods and that do not themselves rely on依靠 conventional传统的 stacks need not supply native method stacks.If supplied, native method stacks are typically通常 allocated分配…(给) per thread when each thread is created.

  This specification规范 permits native method stacks either to be of a fixed固定的 size or to dynamically expand and contract伸缩 as required by the computation计算.If the native method stacks are of a fixed size, the size of each native method stack may be chosen选择 independently独立地 when that stack is created.

  A Java Virtual Machine implementation may provide the programmer or the user control over支配 the initial size of the native method stacks,as well as, in the case of varying-size大小不一 native method stacks, control over支配 the maximum and minimum method stack sizes.

  The following exceptional conditions情况 are associated with与…有关 native method stacks: If the computation in a thread requires a larger native method stack than is permitted, the Java Virtual Machine throws a StackOverflowError. If native method stacks can be dynamically expanded and native method stack expansion is attempted but insufficient不足的 memory can be made available, or if insufficient不足的 memory can be made available to create the initial native method stack for a new thread, the Java Virtual Machine throws an OutOfMemoryError.

《深入理解Java虚拟机》
  Native Method StacksJVM Stacks 所发挥的作用是非常相似的,其区别只是JVM Stacksjvm执行Java方法也就是字节码 服务,而Native Method Stacks是为jvm使用到的Native Method服务。JVM规范对Native Method Stacks中方法使用的语言使用方式数据结构并没有任何强制规定,因此具体的jvm可以根据需要自由实现它,甚至有的JVMHotSpot直接把Native Method StacksJVM Stacks合二为一。与JVM一样,Native Method Stacks也会在stack深度溢出时抛出StackOverflowError或者stack扩展失败时抛出OutOfMemoryError

Native Method Stacks特点

  • 管理Native Method的调用
  • 线程私有
  • 容量允许被实现成固定大小或可动态扩展
  • Native Method InterfaceJNI,本地方法接口
  • Native Method Library 本地方法库

Native Method

A native method is a java method whose implementation is provided by non-java code

概述

  • Native Method是java调用非java代码实现的接口.该方法的实现由非java语言实现例如c或c++
  • 这个特征非java所特有,很多编程语言都有这一机制例如:c++中可以使用extern "C" 告知c++编译器去调用一个c函数
  • 在定义一个native method时,并不提供具体实现有些像定义一个java interface, 其具体实现是由非java语言在jvm外部实现
  • Native Method Interface的作用是融合不同的编程语言为Java所用,初衷是融合c/c++程序

本地方法使用c语言实现时

  • 具体做法是native method stacks中登记native方法
  • Execution engine执行时加载native method library
  • 当某个线程调用一个native method时他就进入了一个全新的并且不再受JVM限制的世界,它和虚拟机拥有同样的权限
    • native method可以通过native method interface来访问JVM内部的runtime data area
    • 它甚至可以直接使用本地处理器中的寄存器
    • 直接从本地内存的堆中分配任意数量的内存

注意:

使用native标识符修饰,不能与abstract修饰符连用

  • java.lang.Object
    • public native int hashCode();
    • public final native void notify();
    • public final native void notifyAll();
  • java.lang.Thread
    • private native void start0();
    • private native void setPriority0(int newPriority);
    • private native void stop0(Object o);
    • private native void suspend0();
    • private native void resume0();
    • private native void interrupt0();
    • private native void setNativeName(String name);

使用原因

  • 主要原因是java应用需要与java外面的环境交互。
    • 有些层次的任务使用java实现起来不容易,或者对程序的效率有影响
    • 当java需要与一些底层系统,如操作系统或某些硬件交换信息时的情况,native method正是这样一种交流机制
    • 通过native method提供一个非常简洁的接口JNI,无需去了解java应用之外的繁琐的细节
  • 与操作系统交互
    • JVM支持java语言本身和运行时库,它是java程序赖以生存的平台,由一个解释器解释字节码和一些连接到本地代码的库组成
    • JVM不是一个完整的系统,经常依赖于一些底层系统的支持。这些底层系统常常是强大的操作系统
    • 通过使用native method得以用java实现了jre与底层系统的交互,甚至 JVM 的一些部分都是 C 写的
    • 如果使用一些java语言本身没有提供封装的操作系统的特性时,我们也需要使用native method
  • sun's java
    • Sun的解释器是用C实现的,这使得它能像一些普通的C一样与外部交互,jre大部分是Java实现的,也有通过一些native method与外界交互
    • 例如java.lang.ThreadsetPriority()方法是由java实现的,
      • 但是setPriority()的实现调用的是java.lang.Threadnative method setPriority0()
      • setPriority0()方法是由C实现的,并被植入JVM内部
      • Windows95的平台上,这个本地方法最终将调用Win32 setPriority() API
    • 这是一个native method的具体实现由JVM直接提供的例子,更多的情况是native methodexternal dynamic link library外部的动态链接库提供,然后被JVM调用
  • 现状
    • 与硬件有关的应用。
      • 停车场管理系统通过JNI与硬件设备交互。
      • 通过java程序驱动打印机
      • Java系统管理生产设备
    • 在企业级应用中比较少见因为现在的异构领域间通信很发达,比如可以使用socket通信,也可以使用Web Service等

java 对象实例化

创建对象的方式

  • new
    • 最常见的 new
    • 单例模式构造器访问权限被设置为私有,通过调用静态方法创建对象
    • XxxBuilder/XxxFactory 工厂模式的静态方法创建对象
  • newInstance()
    • Class的newInstance()
    • jdk9被标记为过时,反射方式,比较苛刻,只能调用空参构造器,且构造器访问权限必须设置为public
  • newInstance(args)
    • Constructor的newInstance(args)
    • 替代Class的newInstance(),反射方式,可以调用无参、有参构造器,且对构造器访问权限没有要求
  • clone()
    • 不调用构造器,当前类需要实现Cloneable接口,实现clone()
  • 反序列化
    • 从文件或网络中获取一个对象的二进制流,将二进制流转换为对象
  • 第三方库 Objenesis

创建对象的步骤

  • 判断对象对应的类是否Loader加载、Linking链接、Initialization初始化
    • JVM遇到一条new指令,首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,
    • 检查这个符号引用代表的类的元信息是否存在即该类是否已经加载、解析、初始化
      • 如果不存在,在双亲委派模式下,使用当前类加载器以ClassLoader+包名+类名为Key进行查找对应的.class文件。
        • 如果没有找到文件,则抛出ClassNotFoundException
        • 如果找到文件,则进行类加载,并生成对应的Class类对象
      • 如果存在,则继续后续步骤
  • 为对象分配内存
    • 如果内存规整
      • 指针碰撞
    • 如果内存不规整
      • 虚拟机需要维护一个列表
      • 空闲列表分配
  • 处理并发安全问题
    • 采用 CAScompare and swap配上失败重试保证更新的原子性 atomic
    • 每个线程预先分配一块线程私有的分配缓冲区TLAB.Thread Local Allocation Buffer
  • 初始化分配到的空间
    • 所有属性设置默认值,保证对象实例字段在不赋值时可以直接使用
  • 设置对象的对象头
  • 执行方法进行初始化

计算实体对象占用内存

实例对象内存计算
实例对象内存计算
数组对象内存计算
数组对象内存计算

  • Head对象头
    • _mark: MarkWord存储对象自身运行时数据
      • 32位系统 占用4byte
      • 64位系统 占用8byte
    • _klass: klass指针,指向该类元数据的指针,jvm通过这个指针确定这个对象是哪个类的实例
      • 32位系统 占用4byte
      • 64位系统
        • 不开启指针压缩 占用8byte
        • 开启指针压缩 占用4byte
    • _length: 数组对象才有,用来记录数组长度。占用4byte
  • Instance Data实例数据
    • 对象真正存储的有效信息,各类型字段内容,父类继承和自己定义的
    • 继承关系
      • 先存放父类中的成员,接着才是子类中的成员,父类要按照8byte规定对齐
      • 例如枚举(相当于继承) 有一个String,具体枚举大小=8+4+(4+4)+4=24
      • markWord=8,klass=4,父类有一个String=4,父类对齐+4=(4+4)=8,此时大小为20,对齐+4=24
  • Padding对齐填充
    • HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,最终字节大小要能被8整除。不能被整除时使用 padding 补位到能被8整除的长度。
    • 访问未对齐的内存,处理器需要作两次内存访问
    • 访问已对齐的内存,处理器仅需要一次内存访问

附录

类型字节数(Byte)位数(bit)取值范围
byte18-2^7 ~ 2^7-1
short216-2^15 ~ 2^15-1
int432-2^31 ~ 2^31-1
long864-2^63 ~ 2^63-1
boolean18true和false
char216unicode编码,前128字节与ASCII兼容字符存储范围在 \u0000~\uFFFF
float4323.402823e+38 ~ 1.401298e-45(e+38表示是乘以10的38次方,同样,e-45表示乘以10的负45次方)
double8641.797693e+308~ 4.9000000e-324
reference4/832/64引用型数据,32位系统或开启指针压缩的64位系统占用4byte,64位系统不开指针压缩占8byte

对象头_mark_klass_length
描述MarkWord 存储对象自身运行时数据指向该类元数据的指针,jvm通过这个指针确定这个对象是哪个类的实例数组对象才有,用来记录数组长度
32位系统4byte4byte4byte
64位系统 开启指针压缩8byte4byte4byte
64位系统 不开启指针压缩8byte8byte4byte
Tags: