Java虚拟机 平行系列二

目录

1、准备工作

2、运行时数据区

2.1、什么是运行时数据区

2.2、运行时数据区可以划分为几个区域?为什么要这么划分?

Heap:堆

Method Area:方法区

Heap和Method Area总结:

Java Virtual Machine Stacks:Java虚拟机堆栈

Native Method Stacks:本地方法堆栈

The pc Register:程序计数器

Run-Time Constant Pool:运行时常量池

2.3 Java虚拟机栈,压栈帧 执行方法的操作过程

3、垃圾回收机制和垃圾回收算法

3.1、垃圾回收机制

3.2、垃圾回收算法

3.2.1、标记-清除

3.2.2、标记-复制

3.2.3、标记-整理


基于咕泡学院Jack老师视频

相关视频:https://ke.qq.com/course/466802?taid=3966307809042290

主要内容:

1、什么是运行时数据区;

2、运行时数据区为什么要这么划分;

3、方法的执行和Java虚拟机栈详解;

4、垃圾回收机制和垃圾回收算法;

1、准备工作

Person.java文件

class Person {
    private String name = "Jack";
    private int age;
    private final double salary = 100;
    private static String address;
    private final static String hobby = "Programming";
    //    private Object obj = new Object();
    public void say() {
        System.out.println("Person say...");
    }
    public static int calc(int op1, int op2) {
        op1 = 3;
        int result = op1 + op2;
//        Object o = obj;
        return result;
    }
    public static void main(String[] args) {
        System.out.println(calc(1,2));
    }
}

通过javac命令生成class文件

文件位置:

2、运行时数据区

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5

Java8 运行时数据区官方文档。

 

2.1、什么是运行时数据区

 

2.2、运行时数据区可以划分为几个区域?为什么要这么划分?

class文件中到底有多少数据类型?

对象,常量,静态变量,普通的成员变量,方法,局部变量,父类 ......

Java官方文档可以看到以下目录:

2.5. Run-Time Data Areas

  • 2.5.1. The pc Register
  • 2.5.2. Java Virtual Machine Stacks
  • 2.5.3. Heap:   对象【普通的成员变量】,数组 --》堆   new Person();   new Student();    Class<Person> clazz;
  • 2.5.4. Method Area:类的信息【创建的时间,元数据信息】、常量、静态变量、即时编译器编译之后的代码
  • 2.5.5. Run-Time Constant Pool
  • 2.5.6. Native Method Stacks

Heap:堆

其实就是存储Java对象的。

The Java Virtual Machine has a heap that is shared among all Java Virtual Machine threads. The heap is the run-time data area from which memory for all class instances and arrays is allocated.

Java虚拟机具有一个在所有Java虚拟机线程之间共享的。堆是运行时数据区,从中分配了所有类实例和数组的内存。

private int age; ----> 普通变量【普通的成员变量】存储在哪里?

如果我要调用普通变量,是不是要先创建对象.age  ---> 随着对象存储在堆内存。

Java虚拟机具有一个在所有Java虚拟机线程之间共享的。堆是运行时数据区,从中分配了所有类实例和数组的内存。

堆是在虚拟机启动时创建的。对象的堆存储由自动存储管理系统(称为垃圾收集器回收;对象永远不会显式释放。Java虚拟机不假定特定类型的自动存储管理系统,可以根据实现者的系统要求选择存储管理技术。堆的大小可以是固定的,也可以根据计算的需要进行扩展,如果不需要更大的堆,则可以将其收缩。堆的内存不必是连续的。

Java虚拟机实现可以为程序员或用户提供对堆的初始大小的控制,并且,如果可以动态扩展或收缩堆,则可以控制最大和最小堆大小。

以下异常情况与堆相关联:

  •         如果计算需要的堆多于自动存储管理系统可以提供的堆,则Java虚拟机将抛出一个 OutOfMemoryError

Method Area:方法区

The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the "text" segment in an operating system process. It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods (§2.9) used in class and instance initialization and interface initialization.

Java虚拟机具有在所有Java虚拟机线程之间共享的方法区域。该方法区域类似于常规语言的编译代码的存储区域,或者类似于操作系统过程中的“文本”段。它存储每个类的结构,例如运行时常量池,字段和方法数据,以及方法和构造函数的代码,包括用于类和实例初始化以及接口初始化的特殊方法(第2.9节)。

Java虚拟机具有在所有Java虚拟机线程之间共享的方法区域。该方法区域类似于常规语言的编译代码的存储区域,或者类似于操作系统过程中的“文本”段。它存储每个类的结构,例如运行时常量池,字段和方法数据,以及方法和构造函数的代码,包括用于类和实例初始化以及接口初始化的特殊方法(第2.9节)。

方法区域是在虚拟机启动时创建的。尽管方法区域在逻辑上是堆的一部分,但是简单的实现可以选择不进行垃圾回收或压缩。该规范没有规定方法区域的位置或用于管理已编译代码的策略。方法区域可以是固定大小的,或者可以根据计算的需要进行扩展,如果不需要更大的方法区域,则可以缩小。方法区域的内存不必是连续的。

Java虚拟机实现可以为程序员或用户提供对方法区域初始大小的控制,以及在方法区域大小可变的情况下,可以控制最大和最小方法区域大小。

以下异常条件与方法区域相关联:

  •        如果无法提供方法区域中的内存来满足分配请求,则Java虚拟机将抛出一个OutOfMemoryError

Heap和Method Area总结:

1、is shared among all java Virtual Machine threads. --->所有线程所共享的区域-->这两块数据中的数据是线程非安全的,因为可能会被多个线程用到同一个数据。

2、The heap is created on virtual machine start-up.  The method area is created on virtual machine start-up.  ---> Heap和Method Area的创建是跟虚拟机进程相关的。

3、If a computation requires more heap than can be made available by the automatic storage management system, the Java Virtual Machine throws an OutOfMemoryError. If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an OutOfMemoryError.  --->  Heap和Method Area的大小可以通过JVM参数来控制,当他们的内存大小不够用的时候,就会抛出 OutOfMemoryError.

Java程序启动的话,会有一个Java进程,Java程序的灵魂应该就是线程的执行。(另一个是面向对象)。

Java Virtual Machine Stacks:Java虚拟机堆栈

方法的执行一定是由线程来执行的。
T1 T2 T3 T4 线程执行方法的过程应该可以用一个数据结构表示:栈 -->  先进后出。

Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at the same time as the thread. 

每个Java虚拟机线程都有一个私有Java虚拟机堆栈,与该线程同时创建。

每个Java虚拟机线程都有一个私有Java虚拟机堆栈,与该线程同时创建。Java虚拟机堆栈存储框架(第2.6节)。Java虚拟机堆栈类似于C之类的常规语言的堆栈:它保存局部变量和部分结果,并在方法调用和返回中起作用。因为除了推送和弹出帧外,从不直接操纵Java虚拟机堆栈,所以可以为堆分配帧。Java虚拟机堆栈的内存不必是连续的。

在第一版中的Java ®虚拟机规范,Java虚拟机堆被称为Java堆栈

该规范允许Java虚拟机堆栈具有固定大小,或者根据计算要求动态扩展和收缩。如果Java虚拟机堆栈的大小固定,则在创建每个Java虚拟机堆栈时可以独立选择它们的大小。

Java虚拟机实现可以为程序员或用户提供对Java虚拟机堆栈的初始大小的控制,并且在动态扩展或收缩Java虚拟机堆栈的情况下,可以控制最大和最小大小。

以下异常条件与Java虚拟机堆栈相关:

  •          如果线程中的计算需要比允许的Java虚拟机更大的堆栈,则Java虚拟机将抛出StackOverflowError
  •          如果可以动态扩展Java虚拟机堆栈,并尝试进行扩展,但是可以提供足够的内存来实现扩展,或者如果没有足够的内存来为新线程创建初始Java虚拟机堆栈,则Java虚拟机机器抛出一个OutOfMemoryError

Native Method Stacks:本地方法堆栈

 

other than the Java programming language). Native method stacks may also be used by the implementation of an interpreter for the Java Virtual Machine's instruction set in a language such as C. Java Virtual Machine implementations that cannot load native methods and that do not themselves rely on conventional stacks need not supply native method stacks. If supplied, native method stacks are typically allocated per thread when each thread is created.

This specification permits native method stacks either to be of a fixed size or to dynamically expand and contract as required by the computation. If the native method stacks are of a fixed size, the size of each native method stack may be chosen independently when that stack is created.

A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the native method stacks, as well as, in the case of varying-size native method stacks, control over the maximum and minimum method stack sizes.

The following exceptional conditions are associated with native method stacks:

  • If the computation in a thread requires a larger native method stack than is permitted, the Java Virtual Machine throws a StackOverflowError.

  • If native method stacks can be dynamically expanded and native method stack expansion is attempted but insufficient memory can be made available, or if insufficient memory can be made available to create the initial native method stack for a new thread, the Java Virtual Machine throws an OutOfMemoryError.

从修饰词Native就可以看出,这个栈是存储C语言方法的地方。

Java虚拟机的实现可以使用俗称“ C堆栈”的常规堆栈来支持native方法(以Java编程语言以外的其他语言编写的方法)。解释程序的实现也可以使用诸如C之类的语言来解释Java虚拟机的指令集,以使用native 本机方法栈。无法加载方法并且自身不依赖于常规堆栈的Java虚拟机实现不需要提供本机方法栈。如果提供,通常在创建每个线程时为每个线程分配本机方法堆栈。

该规范允许本机方法堆栈具有固定大小,或者根据计算要求动态扩展和收缩。如果本机方法堆栈的大小固定,则在创建每个本机方法堆栈的大小时可以独立选择。

Java虚拟机实现可以为程序员或用户提供对本机方法堆栈的初始大小的控制,以及在本机方法堆栈大小变化的情况下,对最大和最小方法堆栈大小的控制。

以下异常条件与本机方法堆栈相关联:

  •           如果线程中的计算需要比允许的更大的本机方法堆栈,则Java虚拟机将抛出StackOverflowError

  •           如果可以动态扩展本机方法堆栈并尝试进行本机方法堆栈扩展,但可以提供足够的内存,或者可以提供足够的内存来为新线程创建初始本机方法堆栈,则Java虚拟机将抛出OutOfMemoryError

The pc Register:程序计数器

The Java Virtual Machine can support many threads of execution at once (JLS §17). Each Java Virtual Machine thread has its own pc (program counter) register. At any point, each Java Virtual Machine thread is executing the code of a single method, namely the current method (§2.6) for that thread. If that method is not native, the pc register contains the address of the Java Virtual Machine instruction currently being executed. If the method currently being executed by the thread is native, the value of the Java Virtual Machine's pc register is undefined. The Java Virtual Machine's pc register is wide enough to hold a returnAddress or a native pointer on the specific platform.

Java虚拟机可以一次支持多个执行线程(JLS§17)。每个Java虚拟机线程都有其自己的 pc(程序计数器)寄存器。在任何时候,每个Java虚拟机线程都在执行单个方法的代码,即该线程的当前方法(第2.6节)。如果不是 native,则该pc寄存器包含当前正在执行的Java虚拟机指令的地址。如果线程当前正在执行的方法是native,则Java虚拟机的pc 寄存器值未定义。Java虚拟机的pc寄存器足够宽,可以returnAddress在特定平台上保存一个或本机指针。

Run-Time Constant Pool:运行时常量池

ranging from numeric literals known at compile-time to method and field references that must be resolved at run-time. The run-time constant pool serves a function similar to that of a symbol table for a conventional programming language, although it contains a wider range of data than a typical symbol table.

Each run-time constant pool is allocated from the Java Virtual Machine's method area (§2.5.4). The run-time constant pool for a class or interface is constructed when the class or interface is created (§5.3) by the Java Virtual Machine.

The following exceptional condition is associated with the construction of the run-time constant pool for a class or interface:

  • When creating a class or interface, if the construction of the run-time constant pool requires more memory than can be made available in the method area of the Java Virtual Machine, the Java Virtual Machine throws an OutOfMemoryError.

See §5 (Loading, Linking, and Initializing) for information about the construction of the run-time constant pool.

运行时常量池是的每个类或每个接口的运行时表示constant_pool在表class文件(§4.4)。它包含多种常量,范围从编译时已知的数字文字到必须在运行时解析的方法和字段引用。运行时常量池的功能类似于常规编程语言的符号表,尽管它包含的数据范围比典型的符号表还大。

每个运行时常量池都是从Java虚拟机的方法区(Method Area 第2.5.4节)分配的。由Java虚拟机创建类或接口(第5.3节)时,将为类或接口构造运行时常量池。

以下异常条件与类或接口的运行时常量池的构造相关联:

  •           创建类或接口时,如果运行时常量池的构造所需的内存超过Java虚拟机的方法区域中可用的内存,则Java虚拟机将抛出OutOfMemoryError

有关 运行时常量池的构造信息请参见§5(加载,链接和初始化

之所以没有运行时常量池,是因为运行时常量池是由方法区分配的,可以理解为运行时常量池包含在方法区中。

方法区,包含了运行时常量池
Method Area
    JDK1.7  之前 --->  Perm Space 永久代
    JDK1.8  之后 --->  Meta Space 元空间

2.3 Java虚拟机栈,压栈帧 执行方法的操作过程

可以对class文件进行反编译,就能够看到字节码指令,就能够看到方法执行的过程

命令:javap -c Person.class

class com.ph.jvm.Person {
  com.ph.jvm.Person();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: ldc           #2                  // String Jack
       7: putfield      #3                  // Field name:Ljava/lang/String;
      10: aload_0
      11: ldc2_w        #4                  // double 100.0d
      14: putfield      #6                  // Field salary:D
      17: return

  public void say();
    Code:
       0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #8                  // String Person say...
       5: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return

  public static int calc(int, int);
    Code:
       0: iconst_3
       1: istore_0
       2: iload_0
       3: iload_1
       4: iadd
       5: istore_2
       6: iload_2
       7: ireturn

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: iconst_1
       4: iconst_2
       5: invokestatic  #10                 // Method calc:(II)I
       8: invokevirtual #11                 // Method java/io/PrintStream.println:(I)V
      11: return
}

下面对calc()方法进行分析:

public static int calc(int op1, int op2) {
        op1 = 3;
        int result = op1 + op2;
//        Object o = obj;
        return result;
    }


public static int calc(int, int);
    Code:
       0: iconst_3     --->  3(int)值入栈。
       1: istore_0     --->  将栈顶int类型值保存到局部变量0中。 # 3这个数值从操作数栈中弹出来,赋值给局部变量中的op1
       2: iload_0      --->  从局部变量0中装载int类型值入栈。
       3: iload_1      --->  从局部变量1中装载int类型值入栈。
       4: iadd         --->  将栈顶两long类型数相加,结果入栈。
       5: istore_2     --->  将栈顶int类型值保存到局部变量2中。
       6: iload_2      --->  从局部变量2中装载int类型值入栈。
       7: ireturn      --->  void函数返回。

3、垃圾回收机制和垃圾回收算法

我有一个块内存区域,存着存着,发现不够用了,怎么办? 报错---> OutOfMemoryError。万一这个内存区域中,有些数据已经彻底没用了,我们把它称作为垃圾,对应的垃圾回收算法对其进行回收。

(1)JVM运行时数据区 ---> 逻辑视图

(2)具体落地到物理内存中 ---> 物理视图 ---> 内存模型  ---> Memory Model

3.1、垃圾回收机制

  • 运行时数据  ---> 内存模型设计
  •     内存中存储数据
  •     什么时候数据会是垃圾
  •     是垃圾之后,得要回收,按照一定的回收算法

内存模型设计:由两个部分的数据非常关键,堆内存和方法区,因为这两个区域的存放的是所有线程共享的数据,因此数据会比较多。而线程私有的数据区如程序计数器,本地方法栈、虚拟机栈里面的数据跟线程的生命周期有关,一旦线程被销毁,这些数据也就被回收了,因此不用过多关注。

创建对象流程图

内存视图:VisualVM  专门用来看内存表现的视图工具,如下图:

3.2、垃圾回收算法

什么样的对象是垃圾?

(1)引用计数法
(2)可达性分析 GC Root

回收算法:3种

3.2.1、标记-清除

通过可达性分析的方式来标记需要回收的对象。

 

清除后:

 

缺点:

  1. 堆中所有的对象都会被扫描一遍,从而才能确定需要回收的对象,比较耗时
  2. 有空间碎片,空间不连续,导致一些打的对象可能分配不到合适的空间,这样就还会触发GC回收,而GC回收是比较耗时的

3.2.2、标记-复制

清除后:

优点:不会有空间碎片;

缺点:浪费了一半的空间;

3.2.3、标记-整理

整理后:

 

堆:Old  Young(Eden、S0和S1)
哪个算法用在哪个区域呢?
不同的代用不同的垃圾回收算法

Young区,复制 ---> 前提条件:每次垃圾回收 存活的对象都比较少  ---> 复制算法
        绝大多数的对象都被回收掉了  --->  一般的对象都是朝生夕死的

垃圾回收的过程,GC日志  文章名:Understanding G1 GC Logs  https://blogs.oracle.com/poonam/understanding-g1-gc-logs

结论:Young区使用复制算法是没有问题的。

Old区:一般是存活时间比较长的,意味着很难被回收 ---> 
        当需要要回收的对象很少时,使用标记整理算法(只标记,不清除);
        当需要回收的对象很多时,使用标记清除算法;

会不会让我去设计垃圾回收算法呢?

落地垃圾回收算法:
垃圾收集器

简介:

G1垃圾收集器的过程:

  • (1)初始标记:使用一个垃圾回收线程,进行初始标记,注意此时其他用户线程暂停了,标记的是什么样的对象是垃圾:标记一下GC Roots能够直接关联到的对象,称之为 Stop the world。
  • (2)并发标记:从GC Roots触发可达性 分析出存活的对象
  • (3)最终标记:因为存活对象和垃圾对象会因为业务代码的执行而发生变动,所以需要最终标记一下,这个过程是由多个GC Thread进行标记的;
  • (4)筛选回收:回收垃圾对象,Stop the world。

评价一个垃圾收集器的好坏标准:
(1)吞吐量:越高越好
(2)停顿时间:越少越好

在线GC网站:gceasy.io   https://gceasy.io/

减少GC的频率,以G1为例
(1)适当地增加堆内存的空间
(2)合理地设置G1垃圾收集器的停顿时间
(3)垃圾回收的临界线:
        -XX:InitiatingHeapOccupancPercent =50(该参数表示堆内存使用超过多少的百分比时会触发GC,默认是45%)
(4)适当增加垃圾回收线程的数量
        -XX:ConcGCThreads =10
        
Young GC:Minor GC
Old GC:Major GC
Young + Old ---> Minor GC + Major GC ---> Full GC

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值