JVM(一) 类加载器、类加载过程、JVM参数设置

JVM

Java编译器

Java源文件在通过编译器之后被编译成相应的.Class文件(字节码文件)

JVM解释器

.Class文件又被JVM中的解释器编译成机器码在不同的操作 系统(Windows、Linux、Mac)上运行。每种操作系统的解释器都是不同的,但基于解释器实现的虚拟机是相同的,这也是Java能够跨平台的原因。

JVM结构

Java程序的具体运行过程如下。
(1)Java源文件被编译器编译成字节码文件。
(2)JVM将字节码文件编译成相应操作系统的机器码。
(3)机器码调用相应操作系统的本地方法库执行相应的方法。
Java 虚 拟 机 包 括 一 个 类 加 载 器 子 系 统 ( Class Loader SubSystem)、运行时数据区(Runtime Data Area)、执行引擎和本地接口库(Native Interface Library)。

类加载器子系统:
类加载器子系统用于将编译好的.Class文件加载到JVM中(后面有详细介绍)

执行引擎和本地方法接口:

执行引擎包括即时编译器和垃圾回收器,即时编译器用于将Java字节码编译成具体的机器码,垃圾回收器用于回收在运行过程中不再使用的对象;
本地接口库用于调用操作系统的本地方法库完成具体的指令操作

运行时数据区:

运行时数据区用于存储在JVM运行过程中产生的数据,包括程序计数器、方法区、本地方法区、虚拟机栈和虚拟机堆;
  • 方法区:存储已被虚拟机加载的类元数据信息(元空间)
  • 堆:存放对象实例,几乎所有的对象实例都在这里分配内存
  • 虚拟机栈:虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息
  • 程序计数器:当前线程所执行的字节码的行号指示器
  • 本地方法栈:本地方法栈则是为虚拟机使用到的Native方法服务

JVM的生命周期

虚拟机的启动

java虚拟机通过引导类加载器(bootstrap ClassLoader)创建一个初始类(initial class)来完成,这个类是由虚拟机的具体实现指定的

虚拟机的执行

一个运行中的java虚拟机有着一个清晰的任务:执行java程序

执行一个java程序的时候,真真正正在执行的是一个叫做java虚拟机进程

虚拟机的退出

有如下几种退出的情况

1.程序正常执行结束

2.程序在运行的过程中遇到了异常或错误而异常终止

3.由于操作系统出现错误而导致java虚拟机进程终止

4.某线程调用Runtime类或System类的exit方法或Runtime类的halt方法


类加载

ClassLoader

类加载器子系统ClassLoader负责加载class文件,class文件在文件开头有特定的文件标示,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。

类加载器分为四种:前三种为虚拟机自带的加载器。

  • 启动类加载器(Bootstrap)C++

    负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类

  • 扩展类加载器(Extension)Java

    负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包

  • 应用程序类加载器(AppClassLoader)Java

    也叫系统类加载器,负责加载classpath(java.class.path)中指定的jar包及目录中class

  • 用户自定义加载器 Java.lang.ClassLoader的子类,用户可以定制类的加载方式

public class LockDemo {
    public static void main(String[] args) {
        //LockDemo 是用户的自定义类 由应用类加载器加载 它的父类加载器是扩展类加载  扩展类的父类加载器是启动类加载器
        //AppClassLoader
        System.out.println(LockDemo.class.getClassLoader());
        //ExtClassLoader
        System.out.println(LockDemo.class.getClassLoader().getParent());
        //启动类加载器是C++实现的  java中无法获取
        System.out.println(LockDemo.class.getClassLoader().getParent().getParent());

        //String是rt包中的类
        System.out.println("===========================");

        System.out.println(String.class.getClassLoader());
        System.out.println(String.class.getClassLoader().getParent());
        System.out.println(String.class.getClassLoader().getParent().getParent());
    }
}

控制台运行效果:符合预期

打印控制台中的sun.misc.Launcher,是一个java虚拟机的入口应用

各种类加载器所加载的文件

public static void main(String[] args) {
    //应用类加载器会加载 当前项目编译后的classess目录下的所有的文件
    System.out.println("AppClassLoader加载的文件: ");
    System.out.println(System.getProperty("java.class.path"));
    System.out.println("ExtClassLoader加载的文件: ");
    System.out.println(System.getProperty("java.ext.dirs"));
    //启动类加载器加载路径
    System.out.println("BootstrapClassLoader加载的文件: ");
    for (URL url : Launcher.getBootstrapClassPath().getURLs()) {
        System.out.println(url);
    }
}

双亲委派模型

classloader的双亲委托机制是指多个类加载器之间存在父子关系的时候,某个class类具体由哪个加载器进行加载的问题。

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上

工作过程:

  • 1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

  • 2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

  • 3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;

  • 4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载

  • 5、如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException

捕获式加载

双亲委派模型好处

安全,可避免用户自己编写的类动态替换Java的核心类,如java.lang.String ,防止内存中出现多份同样的字节码(安全性角度)

通过这种层次模型,可以避免类的重复加载,也可以避免核心类被不同的类加载器加载到内存中造成冲突和混乱,从而保证了Java核心库的安全。


类加载过程

ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。

阶段一:加载阶段

阶段一主要就是生成了Class对象

那么加载的字节码文件来源于哪里?

阶段二:链接

验证

字节码文件在文件开头有特定的文件标识,CA FE BA BE

===================

准备

====================

解析

阶段三:初始化

clinit方法,有类变量的赋值会自动帮我们生成该方法

clinit方法注意点

也就是说:多线程并发情况下一个类只会被加载一次 

===============

一个小测试,非法的前向引用

================ 

init方法

构造器对应字节码文件中 init方法。

任何一个类声明以后,内部至少存在一个类的构造器


PC寄存器

每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,即 将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。

JVM参数设置

堆空间的常用参数设置

Java堆区在JVM启动的时候即被创建,其空间大小也就确定。是JVM管理的最大一块内存空间。堆内存的大小是可以调节的。

怎么对jvm进行调优?通过参数配

官网: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

java代码查看jvm堆的默认值大小:

//堆的最大值,默认是内存的1/4
System.out.println("max : "+Runtime.getRuntime().maxMemory()*1.0/1024/1024+" MB ");
//堆的当前总大小,默认是内存的1/64
System.out.println("init : "+Runtime.getRuntime().totalMemory()*1.0/1024/1024+" MB ");


在哪里设置JVM参数

idea运行时设置方式如下:

不加分号,中间有空格。

-Xms10m  -Xmx30m

重新测试这段代码:

System.out.println("max : " + Runtime.getRuntime().maxMemory() * 1.0 / 1024 / 1024 + " MB ");
System.out.println("init : " + Runtime.getRuntime().totalMemory() * 1.0 / 1024 / 1024 + " MB ");

控制台打印如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值