JVM的东西太多了,我们刚开始学java的时候,就会接触堆、栈,还有方法区,因为我们要知道new出来的对象放在哪里,局部变量放在哪里,static修饰的变量放在哪里。

我从网上截一个图:

这里有三大部分:

  • classloader
  • runtime data area
  • execution engine

classloader就是类加载器。比如说我这里有一个Main.java文件:

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
<font style="color:rgb(77, 77, 77)"><font face="&quot;"><font style="font-size:16px">package com.ocean;
 
public class Main {
 
    public static final int intData = 100;
    public static User user = new User();
 
    public int compute(){
        int a = 1;
        int b = 2;
        int c = (a + b) * 3;
        return c;
    }
 
    public static void main(String[] args) {
            Main main = new Main();
            main.compute();
 
    }
}</font></font></font>

通过javac命令,将其编译成Main.class文件,然后classloader就会加载它。

但是,在这里有三个classloader。

首先是Bootstrap ClassLoader,它load的是java核心包,像java.lang,java.net,java.util,java.io,java.sql包中的class文件。

然后是Extension ClassLoader,它load的是$JAVA_HOME/jre/lib/ext中的class文件,它下的就不是核心库了,其实就是额外的库。

最后是Application ClassLoader。它加载的是类路径下的class。

我们感受一下这三个classloader的存在:

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
    public void printClassLoaders() throws ClassNotFoundException {
 
        System.out.println("Classloader of this class:"
                + Main.class.getClassLoader());
 
        System.out.println("Classloader of Logging:"
                + Logging.class.getClassLoader());
 
        System.out.println("Classloader of ArrayList:"
                + ArrayList.class.getClassLoader());
    }
 
    public static void main(String[] args) throws ClassNotFoundException {
            Main main = new Main();
//            main.compute();
        main.printClassLoaders();
         
    }

Arraylist是由bootstrap classloader加载的,但它是由native code(C和C++)写的,所以展现不出来(null)。

类的加载是用代理模式实现的。

比如JVM委托classloader instance把Main.class加载到内存,那么application classloader会委托父类加载器即extension classloader去加载,extension classloader会再向上委托由父类加载器bootstrap classloader去加载,只有当bootstrap classloader和extension classloader无法加载时,application classloader才会自己加载。

注意,classloader加载的时候会verify和prepare。

verify包括标记为final类型的类是否有子类,类中的final方法是否被子类进行重写,重写是否符合规范等等。

prepare包括为类中的静态变量分配内存空间,被final修饰的static变量直接赋值等。

anyway,Main.class文件被加载到了method area。

我们很早就知道,method area里面会存常量、静态变量和类信息,

我们这里的initData就会存在方法区:

public static final int intData = 100;

这个类信息,包括类的静态变量、初始化代码(静态变量的赋值和静态代码块)、实例变量、实例变量的初始化代码(构造方法)、实例方法、父类引用信息(super)。

今天主要讲的,就是runtime data area。

runtime data area里面的成分,像native method stacks,就没什么好讲的,每一条线程,都会有一个native method stacks,它也被叫做“C stacks”,就是用C语言写的方法。

好,我们进入正题。

我们进入main方法。

一个main方法,就是一条线程。

对于每条线程,都会有一个java virtual machine stack。

这里面,会有一个个stack frame,也就是说,每调用一个方法,就会有一个stack frame压进java virtual machine stack。当方法调用完毕,这个stack frame也就没了。所以这是一个first in last out的stack结构,main方法会最后一个结束,因为它是第一个被压进stack中的。

在每一个stack frame中,有local variable,就是局部变量,operand stack,操作数栈,还有dynamic linking,动态链接,以及method invocation completion,方法出口。

dynamic linking可以解释method override,还要结合 Run-Time Constant Pool来说,我们暂时不讲。

主要了解一下local variable与operand stack。

我们看一下用javap反编译后的jvm指令。

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
Compiled from "Main.java"
public class com.ocean.Main {
  public static final int intData;
 
  public static com.ocean.User user;
 
  public com.ocean.Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
 
  public int compute();
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: iconst_3
       8: imul
       9: istore_3
      10: iload_3
      11: ireturn
 
  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class com/ocean/Main
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #4                  // Method compute:()I
      12: pop
      13: return
 
  static {};
    Code:
       0: new           #5                  // class com/ocean/User
       3: dup
       4: invokespecial #6                  // Method com/ocean/User."<init>":()V
       7: putstatic     #7                  // Field user:Lcom/ocean/User;
      10: return
}

看一下compute方法的执行过程:

iconst_1—>Push the int constant (-1, 0, 1, 2, 3, 4 or 5) onto the operand stack.

把int常量压入操作数栈,这里,值就是1。

istore_1—>Store int into local variable.The “n” must be an index into the local variable array of the current frame . The value on the top of the operand stack must be of type int. It is popped from the operand stack, and the value of the local variable at is set to value.

这个含义就是,把1这个值赋值给local variable a。

2: iconst_2

3: istore_2

这两句和上面一样。

iload_1—>Load int from local variable

加载int值。也就是说, local variable array 中下标为1的值(我们这里也正好是1,顺便说一下,这个array中,下标从0开始,0为this)被推到操作数栈栈顶。

iadd—>Both value1 and value2 must be of type int. The values are popped from the operand stack. The int result is value1 + value2. The result is pushed onto the operand stack.

1和2两个数从操作数栈栈顶弹出,然后相加的结果再压入栈顶。后面一样。最后

11: ireturn—>Return int from method。

返回int值。这里有几个东西。一个是程序计数器(pc register)。

每条线程都有一个pc register。

代码前面的01234就是程序计数器记录的值:

0: iconst_1

1: istore_1

2: iconst_2

3: istore_2

4: iload_1

因为多线程运行的时候,一个方法可能运行到一半,cpu时间片就被别的线程抢走了,到时候这个方法再次抢到cpu时间片时,从哪里再开始运行呢?程序计数器就会做好记录。

修改程序计数器的是执行引擎。

最后就是方法出口,compute方法调用完毕之后该回到哪里去呢?

[Java] 纯文本查看 复制代码
1
2
3
4
5
6
7
public static void main(String[] args) throws ClassNotFoundException {
1            Main main = new Main();
2            main.compute();
3        System.out.println("compute() is over!");
//        main.printClassLoaders();
         
    }

它肯定要回到main方法的第三行位置啊,这是由Normal Method Invocation Completion完成的。我们这里就不会抛什么异常了。

静态变量user存在方法区(存的是堆中User的地址),并且指向堆中的对象User。

堆里面,有young generation和old generation。

young generation包括eden space和survivor space。

survivor space有两个,一个是survivor1,另一个是survivor2。

old generation就是所谓的tenured space。

当我们创建了一个对象,JVM就把它放在eden space当中,伊甸园嘛,就是新生的对象。

eden space也有一个大小的,它满了之后,minor gc就会干活,它会把eden space当中的所有非垃圾对象挪到survivor space,那是survivor1还是survivor2,这不一定的。看名字也知道,这些就是还被引用指向的幸存对象。

一个对象从eden space挪到survivor space一次(比如说是s1),就可以认为年岁加一。

等到下一次eden space又满了,minor gc就会把eden space中的对象和s1中的对象挪到s2,这时最初在eden space中的对象年龄又加1,可以说是两岁了。

这些不死的对象就被minor gc这样在s1和s2之间挪来挪去,年龄不断增加,等到15岁时,就认为是老不死的对象了,并将其挪到老年代(tenured space)。

我们看一段程序:

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
package com.ocean;
 
import java.util.ArrayList;
 
public class TestHeap {
 
    public static final String SUCCESS = "1";
 
    public static void main(String[] args) throws InterruptedException {
        ArrayList list = new ArrayList();
 
        while(true){
            list.add(new TestHeap());
            Thread.sleep(10);
        }
    }
}

然后在visual gc下的分析:

更多java学习资料可关注:itheimaGZ获取

JVM简述的更多相关文章

  1. JVM 简述

    JVM(Java Virtual Machine,Java虚拟机) Java程序的跨平台特性主要是指字节码文件可以在任何具有Java虚拟机的计算机或者电子设备上运行,Java虚拟机中的Java解释器负 ...

  2. JVM学习笔记——内存结构篇

    JVM学习笔记--内存结构篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的内存结构部分 我们会分为以下几部分进行介绍: JVM整体介绍 程序计数器 虚拟机栈 本地方法栈 堆 方法 ...

  3. JVM参数简述

    java虚拟机启动时会带有很多的启动参数,Java命令本身就是一个多参数的启动命令.那么具体JVM启动包含哪些参数呢?这篇文章针对java8的情况做一篇汇总解读,包含大多数常见和不常见的命令参数,过于 ...

  4. JVM内存回收机制简述

    JVM内存回收机制涉及的知识点太多了,了解越多越迷糊,汗一个,这里仅简单做个笔记,主要参考<深入理解Java虚拟机:JVM高级特性与最佳实践(第二版)> 目前java的jdk默认虚拟机为H ...

  5. JVM的粗略简述

    什么是Java虚拟机 虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的.Java虚拟机有自己完善的硬体架构,如处理器.堆栈.寄存器等,还具有相应的指令系统.JVM屏蔽了与 ...

  6. 03 JVM 从入门到实战 | 简述垃圾回收算法

    引言 之前我们学习了 JVM 基本介绍 以及 什么样的对象需要被 GC ,今天就来学习一下 JVM 在判断出一个对象需要被 GC 会采用何种方式进行 GC.在学习 JVM 如何进行垃圾回收方法时,发现 ...

  7. 简述 JVM 垃圾回收算法

    经典垃圾回收 标记-清除(Mark-Sweep) 研发园开了家新餐厅,餐厅老板在考虑如何回收餐盘时首先使用了最简单的方式,那就是服务员在顾客用餐的过程中,不定时的观察餐厅,针对用完餐的顾客记录他们的位 ...

  8. JVM运行时数据区内容简述

    JVM运行时数据区分为五个部分:程序计数器.虚拟机栈.本地方法栈.堆.方法区.如下图所示,五部分其中又分为线程共享区域和线程私有区域,下面将分别介绍每一部分. 1. PC程序计数器 程序计数器是一块较 ...

  9. JVM内存分配及GC简述

    在阐述JVM的内存区域之前,先来看下计算机的存储单位.从小到大依次为Bit,Byte,KB,MB,GB,TB.相邻的单位相差2的10次方. 计算机运行中的存储元件主要分为寄存器(位于CPU)和内存,寄 ...

随机推荐

  1. nodejs(13)模块加载机制

    模块加载机制 优先从缓存中加载 当一个模块初次被 require 的时候,会执行模块中的代码,当第二次加载相同模块的时候,会优先从缓存中查找,看有没有这样的一个模块! 好处:提高模块的加载速度:不需要 ...

  2. 吴裕雄--天生自然MySQL学习笔记:MySQL 事务

    MySQL 事务主要用于处理操作量大,复杂度高的数据.比如说,在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成 ...

  3. 运用Access学习数据库的三大范式

    第一范式(1NF):强调的是列的原子性,即“列不能够再分成其他几列”,同一列中不能有多个值. 例子:业余爱好编码表+员工编码表 当员工杨来的业余爱好有多个时,此时的数据库设计不满足第一范式,可进行如下 ...

  4. IP首部检验和的计算和举例

    IP首部校验和 首部校验和(16位)字段只检验数据报的首部,不检验数据部分.这里不采用CRC检验码而采用简单的计算方法. 发送端 首先将检验和置零,求首部数据的补码和(包含检验和),因为为零,所以无影 ...

  5. java反射修改静态方法的值setAccessible

    这几天闲来无事.在网上看了一个题目,相信大家都知道这个题目  static void change(String str){         str="welcome";     ...

  6. Creo 2.0 Toolkit 解锁的问题

    近期开发Creo Toolkit遇到一个问题,在自己本机开发完成后运行并无问题,但是如果拿去给别人的机子运行会报出 提示“creo ToolKit应用程序在分配到您的地址之前未被解锁”,在与PTC 技 ...

  7. Java并发编程:CountDownLatch、CyclicBarrier和 Semaphore , Condition

    http://www.importnew.com/21889.html 1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同: CountDown ...

  8. PAT Basic 1043 输出PATest (20分)[Hash散列]

    题目 给定⼀个⻓度不超过10000的.仅由英⽂字⺟构成的字符串.请将字符重新调整顺序,按"PATestPATest-."这样的顺序输出,并忽略其它字符.当然,六种字符的个数不⼀定是 ...

  9. Druid数据库连接池获取连接阻塞(转载)

    一. 背景        17年公司有个项目组在南京做项目的时候,开发框架用的是spring boot ,数据库连接池用的是druid,但老是遇到socket read timeout的错误,不得已放 ...

  10. ubuntu下Django的搭建

    工具:Window下的pycharm .VirtualBox下的Ubuntu系统.非必需的Xshell(远程连接工具) 现在针对各种包或python版本不能共存或包不能很好下载的问题,开始流行使用虚拟 ...