JVM-1

今天简单整理了一下 JVM 的类加载机制,主要参考文献:

一道面试题搞懂JVM类加载机制

How JVM Works – JVM Architecture?

先看这道面试题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.heima.jvm;

/**
* @author created by qwb on 2018/10/6 15:19
*/
public class test {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println("Singleton1 value1: "+ singleton.value1);
System.out.println("Singleton1: "+singleton.value2);

Singleton2 singleton2 = Singleton2.getInstance2();
System.out.println("Singleton2 value1: "+singleton2.value1);
System.out.println("Singleton2 value2: "+singleton2.value2);
}
}

class Singleton{
private static Singleton singleton = new Singleton();
public static int value1;
public static int value2= 0;

private Singleton(){
value1++;
value2++;
}
public static Singleton getInstance(){
return singleton;
}
}

class Singleton2{
public static int value1;
public static int value2 = 0;
private static Singleton2 singleton2 = new Singleton2();//与Singleton1的位置不同

private Singleton2(){
value1++;
value2++;
}
public static Singleton2 getInstance2(){
return singleton2;
}
}
/**
* Singleton1 value1: 1
Singleton1: 0
Singleton2 value1: 1
Singleton2 value2: 1
* */

类加载机制图1

jvm加载类

jvm 类加载机制图2

jvm内存模型

下面详细介绍类的加载机制:

加载

加载主要是将.class文件(并不一定是.class。可以是ZIP包,网络中获取)中的二进制字节流读入到JVM中。 在加载阶段,类加载器读取 .class 文件,生成相关联二进制数据,并把它保存在方法区。对于每一个 .class 文件, JVM 保存类的以下信息 到方法区:

  • 所加载类的全限定名以及它的直接父类
  • .class 文件相关的类、接口、枚举
  • 修饰符、变量、方法信息等

在加载 .class 文件后,JVM 在堆内存里新建一个 Class 类型的对象,去代表这个 .class 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.heima.jvm;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
大专栏  JVM-1> * @author created by qwb on 2018/10/6 15:50
*/
public class TestClass {
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
Class c1 = s1.getClass();
Class c2 = s2.getClass();
System.out.println(c1.getName());
System.out.println(c1==c2);
Method[] m = c1.getDeclaredMethods();
for (Method method:m){
System.out.println(method.getName());
}

Field[] f = c1.getDeclaredFields();
for (Field field:f){
System.out.println(field.getName());
}
}
}
class Student{
private String name;

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getRoll_No() {
return roll_No;
}
public void setRoll_No(int roll_No) {
this.roll_No = roll_No;
}
private int roll_No;
}
/**
com.heima.jvm.Student
true //注意: 对于每一个 .class 文件只生成一个 Class 对象
getName
setName
setRoll_No
getRoll_No
name
roll_No
*/

验证

验证是连接阶段的第一步,主要确保加载进来的字节流符合JVM规范。 验证阶段会完成以下4个阶段的检验动作:

1)文件格式验证

2)元数据验证(是否符合Java语言规范)

3)字节码验证(确定程序语义合法,符合逻辑)

4)符号引用验证(确保下一步的解析能正常执行)

准备

主要为静态变量在方法区分配内存,并设置默认初始值

解析

是虚拟机将常量池内的符号引用替换为直接引用的过程

初始化

初始化阶段是类加载过程的最后一步,主要是根据程序中的赋值语句主动为类变量赋值。

初始化执行过程从类的顶部到底部,从父类到子类进行初始化。通常用 3 个类加载器。

启动类加载器 (Bootstrap class loader)

每个 JVM 实现必须有一个启动类加载器,它加载存在于 JAVA_HOME/jre/lib 文件夹下的核心 java 类,这个路径通常称为 启动类路径,启动类加载器是由 C,C++编写的。

扩展类加载器 (Extension class loader)

它是启动类加载器的子类,它加载位于 JAVA_HOME/jre/lib/ext 扩展路径下或者由 java.ext.dirs 所指定的目录路径,它是由 sun.misc.Launcher$ExtClassLoader class 用 java 语言编写的。

应用类加载器 (application class loader)

它是扩展类加载器的子类,它负责加载应用程序类路径下的类,通过使用环境变量映射到 java 类路径,它是由 sun.misc.Launcher$ExtClassLoader class 用 java 语言编写的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.heima.jvm;

/**
* @author created by qwb on 2018/10/6 16:00
*/
public class TestClassloader {
public static void main(String[] args) {
// String class is loaded by bootstrap loader, and
// bootstrap loader is not Java object, hence null
System.out.println(String.class.getClassLoader());
// Test class is loaded by Application loader
System.out.println(TestClassloader.class.getClassLoader());
}
}
/**
* null
sun.misc.Launcher$AppClassLoader@18b4aac2
* */

JVM 内存模型

JVM 内存模型

JVM 内存模型

方法区: 类的所有信息,比如类名、直接父类、方法、变量信息(静态变量)都存储在方法区。每一个 JVM 都只有一个方法区,它是线程共享资源。

堆区:堆区存在所有对象的信息,每一个 JVM 都只只有一个堆区,它是线程共享资源。

Java 虚拟机栈: 对于每一个线程,JVM 都会创建一个运行时栈,该栈的每个块都称为激活记录/栈帧,用于存储方法调用 ,所有的局部变量都会存在这个栈帧里,当一个线程终止后,JVM 将会销毁运行时栈,它是线程私有资源。

程序计数器:它用来存储一个线程的当前运行指令的地址,每一个线程都有一个单独的PC Registers.

本地方法栈:对于每一个线程,都会创建一个单独的本地方法栈,native 方法是用 C 语言实现的,所以也叫 C 栈。

猜你喜欢

转载自www.cnblogs.com/liuzhongrong/p/12408153.html