Java虚拟机原理:JVM为什么被称为机器(machine)? 节选

JVM 主要由类加载器、运行时数据区、执行引擎三个部分组成。(➕类加☁运数📄执引)
image

运行时数据区主要包括方法区、堆、Java 栈、程序计数寄存器。运数:(方method,堆heap,J栈stack,count)

Java 的线程安全常常让人困惑,你可以试着从 Java 栈的角度去理解,
所有在方法内定义的基本类型变量,都会被每个运行这个方法的线程放入自己的栈中🍟
线程的栈彼此隔离,所以这些变量一定是线程安全的。

public class StaticProxyTest {

    public static void main(String[] args) {
        //虽然这个对象被放置在堆中,但是这个对象不会被其他线程访问到,也是线程安全的。
        createSafeSubject();
        //创建实际对象 (类实例对象进入堆中,对象引用被私有进程Java栈独享
        SubjectImpl subject = new SubjectImpl();
        //把实际对象封装到代理对象中
        StaticProxy p = new StaticProxy(subject);
        //执行main中方法,创建新的栈帧囊括该方法,压入栈顶。
        p.request();

    }
	
	/**
     * 如果在方法里创建了一个对象实例,这个对象实例如果没有被方法返回或者放入某些外部的对象容器中的话,
     * 也就是说这个对象的引用没有离开这个方法,虽然这个对象被放置在堆中,
     * 但是这个对象不会被其他线程访问到,也是线程安全的。
	 	下接 【无状态】章节
     */
    public static void createSafeSubject(){
        SafeSubjectImpl safeSubject = new SafeSubjectImpl();
    }
}

如果在方法内创建了一个对象实例,这个对象实例信息将会被存放到堆里,而对象实例的引用,也就是对象实例在堆中的地址信息则会被记录在栈里。堆中记录的对象实例信息主要是成员变量信息,因为类方法内的可执行代码存放在方法区,而方法内的局部变量存放在线程的栈里。

方法区主要存放从磁盘加载进来的类字节码。方:(磁盘加载进来的类字节码bytecode)

在程序运行过程中创建的类实例则存放在堆里。堆:(程序运行过程中创建的类实例class instance)

程序运行的时候,实际上是以线程为单位运行的。thread

当 JVM 进入启动类的 main 方法的时候,就会为应用程序创建一个主线程,main 方法里的代码就会被这个主线程执行
Kotlin springboot main:

@SpringBootApplication
class KotlinSpringbootDemoApplication

fun main(args: Array<String>) {
	runApplication<KotlinSpringbootDemoApplication>(*args)
}

Java springboot main:

@SpringBootApplication
public class EsDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(EsDemoApplication.class, args);
    }

}

Every thread has their single Java stack, there is local variable in the stack when method operation period.
每个线程有自己的 Java 栈,栈里存放着方法运行期的局部变量。

而当前线程执行到哪一行字节码指令,这个信息则被存放在程序计数寄存器。

// class version 52.0 (52)
// access flags 0x21
public class com/example/esdemo/web/CategoryController {

  // compiled from: CategoryController.java

  @Lorg/springframework/stereotype/Controller;()

  // access flags 0x2
  private Lcom/example/esdemo/dao/CategoryDao; categoryDao
  @Lorg/springframework/beans/factory/annotation/Autowired;()

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 26 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/example/esdemo/web/CategoryController; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public index()Ljava/lang/String;
  @Lorg/springframework/web/bind/annotation/GetMapping;(value={""})
   L0
    LINENUMBER 35 L0
    LDC "index"
    ARETURN
   L1
    LOCALVARIABLE this Lcom/example/esdemo/web/CategoryController; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public list()Ljava/lang/String;

一个典型的 Java 程序运行过程是下面这样的。

通过 Java 命令启动 JVM,JVM 的类加载器根据 Java 命令的参数到指定的路径加载.class 类文件,类文件被加载到内存后,存放在专门的方法区。然后 JVM 创建一个主线程执行这个类文件的 main 方法,main 方法的输入参数和方法内定义的变量被压入 Java 栈。如果在方法内创建了一个对象实例,这个对象实例信息将会被存放到堆里,而对象实例的引用,也就是对象实例在堆中的地址信息则会被记录在栈里。堆中记录的对象实例信息主要是成员变量信息,因为类方法内的可执行代码存放在方法区,而方法内的局部变量存放在线程的栈里。


程序计数寄存器一开始存放的是 main 方法的第一行代码位置,JVM 的执行引擎根据这个位置去方法区的对应位置加载这行代码指令,将其解释为自身所在平台的 CPU 指令后交给 CPU 执行。如果在 main 方法里调用了其他方法,那么在进入其他方法的时候,会在 Java 栈中为这个方法创建一个新的栈帧,当线程在这个方法内执行的时候,方法内的局部变量都存放在这个栈帧里。当这个方法执行完毕退出的时候,就把这个栈帧从 Java 栈中出栈,这样当前栈帧,也就是堆栈的栈顶就又回到了 main 方法的栈帧,使用这个栈帧里的变量,继续执行 main 方法。这样,即使 main 方法和 f 方法都定义相同的变量,JVM 也不会弄错。这部分内容我们在第一篇已经讨论过,JVM 作为一个 machine,和操作系统的处理线程栈的的方法是一样的。

image

无状态:
相反,像 Servlet 这样的类,在 Web 容器中创建以后,会被传递给每个访问 Web 应用的用户线程执行,这个类就不是线程安全的。但这并不意味着一定会引发线程安全问题,如果 Servlet 类里没有成员变量,即使多线程同时执行这个 Servlet 实例的方法,也不会造成成员变量冲突。这种对象被称作无状态对象,也就是说对象不记录状态,执行这个对象的任何方法都不会改变对象的状态,也就不会有线程安全问题了。事实上,Web 开发实践中,常见的 Service 类、DAO 类,都被设计成无状态对象,所以虽然我们开发的 Web 应用都是多线程的应用,因为 Web 容器一定会创建多线程来执行我们的代码,但是我们开发中却可以很少考虑线程安全的问题。

posted @ 2022-08-18 11:16  ukyo--君君小时候  阅读(93)  评论(0编辑  收藏  举报