简单的梳理JVM(三)——JVM内存结构

前言

最近闲来无事,索性去学习了一下JVM的基础知识。虽然知识浅显的学习,但是收获还是非常大的,于是决定记录下来。

一、简介

  • 简单来说,JVM就是java虚拟机。它是是由软件技术模拟出计算机运行的一个虚拟的计算机。
  • 主要作用是把我们编写的java程序,编译成.Class文件,然后读取,并解释给操作系统执行。

二、JVM的介绍

JVM生命周期

  • JVM在Java程序开始执行的时候,它才运行,程序结束的时它就停止。

JVM进程和线程

  • 一个Java程序会开启一个JVM进程,如果一台机器上运行三个程序,那么就会有三个运行中的JVM进程。
  • JVM中的线程分为两种:守护线程和普通线程 守护线程是JVM自己使用的线程,比如垃圾回收(GC)就是一个守护线程。
  • 普通线程一般是Java程序的线程,只要JVM中有普通线程在执行,那么JVM就不会停止。 权限足够的话,可以调用exit()方法终止程序。

三、类的生命周期

1.类的概念

  • 将类的.class文件中的二进制数据读入到内存中,将其放入数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。
  • 类的加载的最终产品是位于堆区中的 Class对象。
  • Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

2. 生命周期

      我们编写好的java源文件,在经过编译后会生成一个后缀名为class的文件(字节码文件)。而类的加载,就是对class文件的加载。

      生命周一共分为加载、连接、初始化、使用、和卸载五个阶段。

在这里插入图片描述

2.1 加载

主要功能:查找并加载类的二进制数据到方法区中,并在堆中实例化一个Class对象。

  1. 通过一个类的全限定名来获取其定义的二进制字节流。
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  3. 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

2.2 连接

主要功能:做一些加载后的验证工作以及一些初始化前的准备工作,可以细分为三个步骤:验证、准备和解析。

2.2.1 验证

确保被加载的类的正确性

  • 文件格式验证:验证字节流是否符合Class文件格式的规范;
  • 元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。
  • 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
  • 符号引用验证:确保解析动作能正确执行。

2.2.2 准备

为类的静态变量分配内存,并将其初始化为默认值

2.2.3 解析

把符合引用转换为直接引用。
在解析阶段jvm会将所有的类或接口名、字段名、方法名转换为具体的内存地址。
举个栗子:

比如我们要在内存中找一个类里面的一个叫做show的方法,显然是找不到。但是在解析阶段,jvm就会把show这个名字转换为指向方法区的的一块内存地址,比如c17164,通过c17164就可以找到show这个方法具体分配在内存的哪一个区域了。
这里show就是符号引用,而c17164就是直接引用。

2.3 初始化

主要功能:对类的静态变量,静态代码块执行初始化操作
初始化顺序:静态变量/静态代码块 -> main方法 -> 非静态变量/代码块 -> 构造方法

举个栗子

class Parent {
	/* 静态变量 */
	public static String p_StaticField = "父类--静态变量";
	/* 变量 */
	public String p_Field = "父类--变量";
	protected int i = 9;
	protected int j = 0;
	/* 静态初始化块 */
	static {
		System.out.println( p_StaticField );
		System.out.println( "父类--静态初始化块" );
	}
	/* 初始化块 */
	{
		System.out.println( p_Field );
		System.out.println( "父类--初始化块" );
	}
	/* 构造器 */
	public Parent()
	{
		System.out.println( "父类--构造器" );
		System.out.println( "i=" + i + ", j=" + j );
		j = 20;
	}
}

public class SubClass extends Parent {
	/* 静态变量 */
	public static String s_StaticField = "子类--静态变量";
	/* 变量 */
	public String s_Field = "子类--变量";
	/* 静态初始化块 */
	static {
		System.out.println( s_StaticField );
		System.out.println( "子类--静态初始化块" );
	}
	/* 初始化块 */
	{
		System.out.println( s_Field );
		System.out.println( "子类--初始化块" );
	}
	/* 构造器 */
	public SubClass()
	{
		System.out.println( "子类--构造器" );
		System.out.println( "i=" + i + ",j=" + j );
	}


	/* 程序入口 */
	public static void main( String[] args )
	{
		System.out.println( "子类main方法" );
		new SubClass();
	}
}

初始化顺序:

父类–静态变量/父类–静态初始化块
子类–静态变量/子类–静态初始化块
父类–变量/父类–初始化块
父类–构造器
子类–变量/子类–初始化块
子类–构造器

2.4 使用

类的使用包括主动引用和被动引用

2.4.1 主动使用

  • new 一个类的时候
  • 调用类的静态方法,以及读取或者修改一个类的静态字段的时候(不是常量)
  • 这个类是程序的入口类
  • 对这个类进行反射的时候(执行了上面的行为)

主动引用会引起类的初始化,这里的几种情况只有new或者使用class.newInstance()才会调用构造函数!

2.4.2 被动使用

  • 引用父类的静态字段
  • 定义类数组
  • 引用类的常量

被动引用不会引起类的初始化。

2.5 卸载

以下情况下,会触发卸载

  • java堆中不存在该类的任何实例。
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法

总结

本章主要内容梳理:

  1. 了解什么是JVM以及其生命周期
  2. 重点理解JAVA的生命周期(加载,连接,初始化,使用,卸载)

后续还有更新,有兴趣可以继续看下去~

参考:
https://www.cnblogs.com/one-reader/p/12251550.html
https://blog.csdn.net/qq_34996727/article/details/80684788
https://www.cnblogs.com/wangsen/p/10838733.html

posted @ 2020-02-02 11:50  笑里笑外~  阅读(130)  评论(0编辑  收藏  举报