JVM虚拟机

1:java概述

特点:
一处编译随处运⾏
管理内存分配和内存回收
不容易出现内存泄漏和内存溢出等问题

2:JVM生命周期

1:启动。启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点。
2:运行。main()作为该程序初始线程的起点,任何其他线程均由该线程启动。
3:消失。当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出。

Java中的线程分为两种:守护线程 (daemon)和普通线程(non-daemon)。守护线程是Java虚拟机自己使用的线程,比如负责垃圾收集的线程就是一个守护线程。当然,你也可以把自己的程序设置为守护线程。包含main()方法的初始线程不是守护线程。

只要Java虚拟机中还有普通的线程在执行,Java虚拟机就不会停止。如果有足够的权限,你可以调用exit()方法终止程序。

3. JVM体系结构

1类装载器(ClassLoader)(用来装载.class文件)
2 执行引擎(执行字节码,或者执行本地方法)
3 运行时数据区(方法区、堆、java栈、PC寄存器、本地方法栈)

JVM虚拟机

3. JVM运行时数据区

Java对象实例存放在堆中常量存放在方法区的常量池;虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据放在方法区;方法区和堆是线程共享的。栈是线程私有的,存放该方法的局部变量表(基本类型、对象引用)、操作数栈、动态链接、方法出口等信息。除了程序技术器不会发生内存溢出,其它都会发生内存溢出。当在堆中没有内存完成实例分配,且堆也无法再扩展时。当方法区无法满足内存的分配需求时,OutOfMemoryError异常
一个Java程序对应一个JVM,一个方法(线程)对应一个Java栈。

JVM虚拟机

程序计数器(Program Counter Register)
为了线程切换后能恢复到正确的执⾏位置
当前线程所执行的字节码的行号指示器(代码控制,多线程切换,生命周期

java虚拟机栈(VM Stack)
作⽤于⽅法执行的一块Java内存区域
每个⽅法在执行的同时都会创建⼀个栈帧(Stack Framel)⽤于存储局部变量表、操作数 栈、动态链接、⽅法出⼝等信息。每⼀个⽅法从调⽤直⾄执⾏完成的过程,就对应着⼀个栈 帧在虚拟机栈中⼊栈到出栈的过程

本地方法栈(Native Method Stack)
非java语言实现的

用来存储对象实例和数组。可以通过-Xmx和-Xms控制堆的大小。
java堆还可以细分为:新生代(New/Young)、旧生代/年老代(Old/Tenured)。持久代(Permanent)在方法区,不属于Heap。jdk1.8将方法区移到堆外,叫做元空间。被加载的类作为元数据加载到底层操作系统的本地内存区。
  java堆是垃圾收集器管理的主要区域。
方法区(堆内)
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
这区域的内存回收⽬标主要是针对常量池的回收和对类型的卸载
运⾏时常量池是⽅法区的⼀部分,Class文件除了有类的版本、字段、方法、接口等描述信息 外,还有⼀项信息是常量池,⽤于存放编译器⽣成的各种字⾯量和符号引用,这部分内容将 在类加载后进⼊⽅法区的运⾏时常量池中存放。final修饰在常量池,static修饰在方法区,普通对象在堆

4.什么是类加载机制?

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
类加载过程
加载(类加载到方法区,生成class对象)
“加载”是“类加载”(Class Loading)过程的一个阶段。在加载阶段,虚拟机需要完成以下3件事情:

1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
验证
确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。内存分配的仅包括类变量(static 修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。
解析
解析阶段是虚拟机常量池内的符号引用替换为直接引用的过程。
1.类、接口的解析
2.字段解析
3.类方法解析
4.接口方法解析
初始化
初始化阶段是执行类构造器< clinit >()方法的过程
1.遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需先触发其初始化。生成这4条指令的最常见的java代码场景是:使用new关键字实例化对象、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用类的静态方法的时候。
2.使用java.lang.reflect包的方法对类进行反射调用的时候
3.当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先触发其父类的初始化
4.jvm启动时,用户指定一个执行的主类(包含main方法的那个类),虚拟机会先初始化这个类

5.对象创建底层步骤细节

JVM虚拟机
虚拟机遇到⼀条new指令时,⾸先检查这个对应的类能否在常量池中定位到一个类的符号引 ⽤
判断这个类是否已被加载、解析和初始化 为这个新⽣对象在Java堆中分配内存空间,其中Java堆分配内存空间的⽅式主要有以下两种
指针碰撞
分配内存空间包括开辟⼀块内存和移动指针两个步骤 非原子步骤可能出现并发问题,Java虚拟机采⽤CAS配上失败重试的方式保证更新 操作的原⼦性
空闲列表
分配内存空间包括开辟⼀块内存和修改空闲列表两个步骤 ⾮原⼦步骤可能出现并发问题,Java虚拟机采⽤CAS配上失败重试的⽅式保证更新 操作的原子性
将分配到的内存空间都初始化为零值
设置对象头相关数据
GC分代年年龄 对象的哈希码 hashCode 元数据信息
执⾏对象方法

6.对象访问定位⽅式

通过虚拟机栈中的reference类型数据来操作堆上的对 象。

  1. 使⽤句柄访问对象。即reference中存储的是对象句柄的地址,⽽句柄中包含了对象实例数据 与类型数据的具体地址信息,相当于⼆级指针。
  2. 直接指针访问对象。即reference中存储的就是对象地址,相当于一级指针。
    对比
    垃圾回收分析:当垃圾回收移动对象时,reference中存储的地址是稳定的地址,不需要修改,仅需要修改对象句柄的地址;⽅式 二垃圾回收时需要修改reference中存储的地址。
    访问效率分析,⽅式二优于⽅式一,因为⽅式⼆只进行了一次指针定位,节省了了时间开销, 而这也是HotSpot采⽤的实现⽅式。
    JVM虚拟机
    JVM虚拟机