java虚拟机(上篇)内存模型

目录

 

jvm与jdk、jre关系图

JVM的生命周期

JVM内存结构

虚拟机运行时内存空间分配

程序计数器

本地方法栈

元空间(方法区)

常量池详解

(GC)垃圾回收器

垃圾收集器分类

解析器和编译器


jvm与jdk、jre关系图

 

JDK:Java Development ToolKit(Java开发工具包)。JDK是整个JAVA的核心,包括了Java运行环境(Java Runtime Envirnment),一堆Java工具(javac/java/jdb等)和Java基础的类库(即Java API 包括rt.jar

JRE:java  Runtime  Enviromental(java运行时环境),包括JVM和JAVA核心类库和支持文件。

JVM:Java Virtual Mechinal(JAVA虚拟机)。JVM是JRE的一部分,它是一个虚构出来的计算机,JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM 的主要工作是解释自己的指令集(即字节码)并映射到本地的 CPU 的指令集或 OS 的系统调用,Java语言是跨平台运行的。

总结:jdk=jre+工具类(javac等)

   jre=jvm+核心类库

JVM的生命周期

  • JVM实例的诞生

当启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为

JVM实例运行的起点

  • JVM实例的运行

 main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,

main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以标明自己创建的线程是守护线程。   

  • JVM实例的消亡

 当程序中的所有“非守护线程” 都终止时,JVM才退出;若安全管理器允许,程序也可以使用java.lang.Runtime类

或者java.lang.System.exit()来退出。

 

举例

static class JvmLifeTest extends TimerTask{

    /**
     * The action to be performed by this timer task.
     */
    @Override
    public void run() {
        System.out.println("用户线程---非守护线程");
    }
}

public static void main(String[] args) throws InterruptedException {
    JvmLifeTest lifeTest = new JvmLifeTest();
    //定时器
    Timer timer = new Timer();
    //非守护线程
    timer.schedule(lifeTest,new Date());
    System.out.println("main  结束啦");
}

 

JVM内存结构

  • 类加载子系统

启动(Bootstrap)类加载器

引导类加载器是用 本地代码实现的类加载器,它负责将 <JAVA_HOME>/lib下面的核心类库 或 -Xbootclasspath

选项指定的jar包等 虚拟机识别的类库 加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,

开发者无法直接获取到启动类加载器的引用,所以 不允许直接通过引用进行操作。

扩展(Extension)类加载器
       扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的,

它负责将 <JAVA_HOME >/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库 加载到内存中。

开发者可以直接使用标准扩展类加载器。

系统(System、Application)类加载器
       系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,

它负责将 用户类路径(java -classpath或-Djava.class.path变量所指的目录,即当前类所在路径及其引用的第三方类库的路径)

下的类库 加载到内存中。开发者可以直接使用系统类加载器。

     

代码展示:

public static void main(String[] args) {
    //bootstrap classloader
    URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
    for(URL url : urLs){
        System.out.println(url);
    }
    System.out.println("----------------华丽的分割线1--------------");
    //ext classloader
    URLClassLoader extClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader().getParent();
    urLs = extClassLoader.getURLs();
    for(URL url : urLs){
        System.out.println(url);
    }
    System.out.println("----------------华丽的分割线2--------------");
    //application classloader
    URLClassLoader appClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
    urLs = appClassLoader.getURLs();
    for(URL url : urLs){
        System.out.println(url);
    }
}

结果:

file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/classes
----------------华丽的分割线--------------
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunec.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/nashorn.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/cldrdata.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/jfxrt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/dnsns.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/localedata.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/jaccess.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/zipfs.jar
file:/System/Library/Java/Extensions/MRJToolkit.jar
----------------华丽的分割线--------------
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/deploy.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/cldrdata.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/dnsns.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/jaccess.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/jfxrt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/localedata.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/nashorn.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunec.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/zipfs.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/javaws.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jfxswt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/management-agent.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/plugin.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/ant-javafx.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/dt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/javafx-mx.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/jconsole.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/packager.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/sa-jdi.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/tools.jar
file:/Users/xiahui/study/project/gitee-activity/target/classes/
file:/Users/xiahui/applications/mavenLib-study/com/baomidou/mybatis-plus/3.0.3/mybatis-plus-3.0.3.jar
file:/Users/xiahui/applications/mavenLib-study/com/baomidou/mybatis-plus-extension/3.0.3/mybatis-plus-extension-3.0.3.jar
file:/Users/xiahui/applications/mavenLib-study/com/baomidou/mybatis-plus-core/3.0.3/mybatis-plus-core-3.0.3.jar
file:/Users/xiahui/applications/mavenLib-study/com/baomidou/mybatis-plus-annotation/3.0.3/mybatis-plus-annotation-3.0.3.jar
file:/Users/xiahui/applications/mavenLib-study/com/github/jsqlparser/jsqlparser/1.2/jsqlparser-1.2.jar
file:/Users/xiahui/applications/mavenLib-study/org/mybatis/mybatis-spring/1.3.2/mybatis-spring-1.3.2.jar
file:/Users/xiahui/applications/mavenLib-study/com/baomidou/mybatis-plus-generator/3.0.3/mybatis-plus-generator-3.0.3.jar
file:/Users/xiahui/applications/mavenLib-study/org/activiti/activiti-engine/5.15.1/activiti-engine-5.15.1.jar
file:/Users/xiahui/applications/mavenLib-study/org/apache/commons/commons-email/1.2/commons-email-1.2.jar
file:/Users/xiahui/applications/mavenLib-study/javax/mail/mail/1.4.1/mail-1.4.1.jar
file:/Users/xiahui/applications/mavenLib-study/javax/activation/activation/1.1/activation-1.1.jar
file:/Users/xiahui/applications/mavenLib-study/org/apache/commons/commons-lang3/3.1/commons-lang3-3.1.jar
file:/Users/xiahui/applications/mavenLib-study/org/mybatis/mybatis/3.2.5/mybatis-3.2.5.jar
file:/Users/xiahui/applications/mavenLib-study/joda-time/joda-time/2.1/joda-time-2.1.jar
file:/Users/xiahui/applications/mavenLib-study/org/slf4j/jcl-over-slf4j/1.7.6/jcl-over-slf4j-1.7.6.jar
file:/Users/xiahui/applications/mavenLib-study/org/activiti/activiti-spring/5.15.1/activiti-spring-5.15.1.jar
file:/Users/xiahui/applications/mavenLib-study/org/activiti/activiti-bpmn-model/5.15.1/activiti-bpmn-model-5.15.1.jar
file:/Users/xiahui/applications/mavenLib-study/org/codehaus/jackson/jackson-core-asl/1.9.9/jackson-core-asl-1.9.9.jar
file:/Users/xiahui/applications/mavenLib-study/org/activiti/activiti-bpmn-layout/5.15.1/activiti-bpmn-layout-5.15.1.jar
file:/Users/xiahui/applications/mavenLib-study/org/tinyjee/jgraphx/jgraphx/1.10.4.1/jgraphx-1.10.4.1.jar
file:/Users/xiahui/applications/mavenLib-study/org/activiti/activiti-bpmn-converter/5.15.1/activiti-bpmn-converter-5.15.1.jar
file:/Users/xiahui/applications/mavenLib-study/org/activiti/activiti-process-validation/5.15.1/activiti-process-validation-5.15.1.jar
file:/Users/xiahui/applications/mavenLib-study/mysql/mysql-connector-java/5.1.29/mysql-connector-java-5.1.29.jar
file:/Users/xiahui/applications/mavenLib-study/org/springframework/spring-core/3.2.2.RELEASE/spring-core-3.2.2.RELEASE.jar
file:/Users/xiahui/applications/mavenLib-study/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.jar
file:/Users/xiahui/applications/mavenLib-study/org/springframework/spring-beans/3.2.2.RELEASE/spring-beans-3.2.2.RELEASE.jar
file:/Users/xiahui/applications/mavenLib-study/org/springframework/spring-test/3.2.2.RELEASE/spring-test-3.2.2.RELEASE.jar
file:/Users/xiahui/applications/mavenLib-study/commons-dbcp/commons-dbcp/1.4/commons-dbcp-1.4.jar
file:/Users/xiahui/applications/mavenLib-study/commons-pool/commons-pool/1.5.4/commons-pool-1.5.4.jar
file:/Users/xiahui/applications/mavenLib-study/org/springframework/spring-context/3.2.2.RELEASE/spring-context-3.2.2.RELEASE.jar
file:/Users/xiahui/applications/mavenLib-study/org/springframework/spring-expression/3.2.2.RELEASE/spring-expression-3.2.2.RELEASE.jar
file:/Users/xiahui/applications/mavenLib-study/org/springframework/spring-context-support/3.2.2.RELEASE/spring-context-support-3.2.2.RELEASE.jar
file:/Users/xiahui/applications/mavenLib-study/org/springframework/spring-tx/3.2.2.RELEASE/spring-tx-3.2.2.RELEASE.jar
file:/Users/xiahui/applications/mavenLib-study/aopalliance/aopalliance/1.0/aopalliance-1.0.jar
file:/Users/xiahui/applications/mavenLib-study/org/springframework/spring-orm/3.2.2.RELEASE/spring-orm-3.2.2.RELEASE.jar
file:/Users/xiahui/applications/mavenLib-study/org/springframework/spring-aop/3.2.2.RELEASE/spring-aop-3.2.2.RELEASE.jar
file:/Users/xiahui/applications/mavenLib-study/org/springframework/spring-aspects/3.2.2.RELEASE/spring-aspects-3.2.2.RELEASE.jar
file:/Users/xiahui/applications/mavenLib-study/org/aspectj/aspectjweaver/1.7.2/aspectjweaver-1.7.2.jar
file:/Users/xiahui/applications/mavenLib-study/org/springframework/spring-jdbc/3.2.2.RELEASE/spring-jdbc-3.2.2.RELEASE.jar
file:/Users/xiahui/applications/mavenLib-study/org/springframework/spring-web/3.2.2.RELEASE/spring-web-3.2.2.RELEASE.jar
file:/Users/xiahui/applications/mavenLib-study/org/springframework/spring-webmvc/3.2.2.RELEASE/spring-webmvc-3.2.2.RELEASE.jar
file:/Users/xiahui/applications/mavenLib-study/junit/junit/4.11/junit-4.11.jar
file:/Users/xiahui/applications/mavenLib-study/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar
file:/Users/xiahui/applications/mavenLib-study/org/slf4j/slf4j-api/1.7.6/slf4j-api-1.7.6.jar
file:/Applications/IntelliJ%20IDEA.app/Contents/lib/idea_rt.jar

虚拟机运行时内存空间分配

图1

 

public class JvmTest {

    protected int hi(){
        int a = 1;
        int b = 2;
        int c = (a+b)*10;
        return c;
    }

    public static void main(String[] args) {
        JvmTest test = new JvmTest();
        int hi = test.hi();
        System.out.println(hi);
    }

}

 

编码java类为字节码

图2

 

看不懂,我们通过javap -v反汇编(JvmTest.txt)

Classfile /Users/xiahui/study/project/gitee-activity/src/main/java/gitee/activity/jvm/JvmTest.class
  Last modified 2020年5月6日; size 513 bytes
  MD5 checksum 341dec80192863ac2dec469d26d36eb7
  Compiled from "JvmTest.java"
public class gitee.activity.jvm.JvmTest
  minor version: 0
  major version: 55
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #2                          // gitee/activity/jvm/JvmTest
  super_class: #7                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 3, attributes: 1
Constant pool:
   #1 = Methodref          #7.#18         // java/lang/Object."<init>":()V
   #2 = Class              #19            // gitee/activity/jvm/JvmTest
   #3 = Methodref          #2.#18         // gitee/activity/jvm/JvmTest."<init>":()V
   #4 = Methodref          #2.#20         // gitee/activity/jvm/JvmTest.hi:()I
   #5 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #6 = Methodref          #23.#24        // java/io/PrintStream.println:(I)V
   #7 = Class              #25            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               hi
  #13 = Utf8               ()I
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               SourceFile
  #17 = Utf8               JvmTest.java
  #18 = NameAndType        #8:#9          // "<init>":()V
  #19 = Utf8               gitee/activity/jvm/JvmTest
  #20 = NameAndType        #12:#13        // hi:()I
  #21 = Class              #26            // java/lang/System
  #22 = NameAndType        #27:#28        // out:Ljava/io/PrintStream;
  #23 = Class              #29            // java/io/PrintStream
  #24 = NameAndType        #30:#31        // println:(I)V
  #25 = Utf8               java/lang/Object
  #26 = Utf8               java/lang/System
  #27 = Utf8               out
  #28 = Utf8               Ljava/io/PrintStream;
  #29 = Utf8               java/io/PrintStream
  #30 = Utf8               println
  #31 = Utf8               (I)V
{
  public gitee.activity.jvm.JvmTest();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 10: 0

  protected int hi();
    descriptor: ()I
    flags: (0x0004) ACC_PROTECTED
    Code:
      stack=2, locals=4, args_size=1
         0: iconst_1
         1: istore_1
         2: iconst_2
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: bipush        10
         9: imul
        10: istore_3
        11: iload_3
        12: ireturn
      LineNumberTable:
        line 13: 0
        line 14: 2
        line 15: 4
        line 16: 11

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #2                  // class gitee/activity/jvm/JvmTest
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #4                  // Method hi:()I
        12: istore_2
        13: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
        16: iload_2
        17: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
        20: return
      LineNumberTable:
        line 20: 0
        line 21: 8
        line 22: 13
        line 23: 20
}
SourceFile: "JvmTest.java"

 

简单解释下hi()方法的执行过程:

操作数栈(先进后出)

从序号0开始:

0:iconst_1 : 将int类型的常量1压入栈

1:istore_1:将int类型的变量1存入局部变量表

2:iconst_2:将int类型的常量2压入栈

3:istore_2:将int类型的变量2存入局部变量表

4:iload_1:将局部变量表中的1压入操作数栈中

5:iload_2:将局部变量表中的2压入到操作数栈

6:iadd:执行int类型的加法运算(在运行的时候将操作数栈中最接近栈顶的两个 int 数值元素出栈相加,然后将相加结果入栈)

7:bipush   10:将int类型的常量10压入操作数栈    (这里其实做了2步操作,第1从常量取值,第2放入操作数栈,所以序号7过了,就是9了,而没有8,这2步在目前的jdk版本已经合并为1步)

9:imul:执行int类型的乘法操作(在运行的时候将操作数栈中最接近栈顶的两个 int 数值元素出栈相乘,然后将相加结果入栈)

10:istore_3:将int类型的变量3(30)存入局部变量

11:iload_3:将局部变量3(30)压入操作数栈

12:ireturn:将int类型的数据返回

 

更多汇编指令

程序计数器

计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器的值则为 (Undefined);线程私有的,上述反汇编语言的序号0、1、2 ...20都是理解为程序计数器存的值,表示当前线程执行的位置,以便线程切换时候切换为上次执行的地方继续执行。

程序计数器特点

1 线程私有的,每个线程都有一个程序计数器.
2 是java虚拟机规范里面, 唯一 一个 没有规定任何 OutOfMemoryError 情况的区域
3 生活周期随着线程的创建而创建,随着线程的结束而消亡.
4 程序计数器是一块较小的内存区域。

本地方法栈

本地方法栈则为虚拟机使用到的 Native 方法服务,比如Thread底层会调用C的本地方法,java底层很多本地方法通过C来实现的。

图3

 

每个方法在执行时都会创建一个栈帧(Stack Frame)用于存储局部变量表操作数栈动态链接方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。(栈先进后出)

栈桢:

栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构。它是虚拟机运行时数据区中的java虚拟机栈的栈元素。
栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。
每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机里面从入栈到出栈的过程。

局部变量表

每个栈帧中都包含一组称为局部变量表的变量列表,用于存放方法参数和方法内部定义的局部变量;

Java 虚拟机规范允许 Slot 的长度可以随着处理器、操作系统或者虚拟机的不同而发生变化。对于64位的数据类型,虚拟机会以高位在前的方式为其分配两个连续的 Slot 空间。即 long 和 double 两种类型。做法是将 long 和 double 类型速写分割为32位读写的做法。不过由于局部变量表建立在线程的堆栈上,是线程的私有数据,无论读写两个连续的 Slot 是否是原子操作,都不会引起数据安全问题。

操作数栈:

操作数栈是一个后入先出(Last In First Out)栈,方法的执行操作在操作数栈中完成,每一个字节码指令往操作数栈进行写入和提取的过程,就是入栈和出栈的过程。操作数栈的每一个元素可以是任意的 Java 数据类型,32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2,在方法执行的任何时候,操作数栈的深度都不会超过在 max_stacks 数据项中设定的最大值(指的是进入操作数栈的 “同一批操作” 的数据类型的栈容量的和)。

动态链接

每个栈帧都包含一个指向运行时常量池(JVM 运行时数据区域)中该栈帧所属方法属性的引用,持有这个引用是为了支持方法调用过程中的动态连接。

在 Class 文件格式的常量池(存储字面量和符号引用)中存有大量的符号引用(1.类的全限定名,2.字段名和属性,3.方法名和属性),字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载过程的解析阶段的时候转化为直接引用(指向目标的指针、相对偏移量或者是一个能够直接定位到目标的句柄),这种转化称为静态解析。另外一部分将在每一次的运行期期间转化为直接引用,这部分称为动态连接。

比如

 

方法出口:

在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的程序计数器的值可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。

通俗点就是:方法退出的过程实际上就等同于把当前栈帧出栈,方法退出需要返回到方法被调用的位置,保证程序继续执行。

元空间(方法区)

jdk1.8一以前叫做方法区,jdk1.8时候叫元空间,主要是存在类元信息(上述的JvmTest.class)、常量、静态变量。

常量池详解

Class文件中的常量池:

常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:

类和接口的全限定名
字段名称和描述符
方法名称和描述符

 

运行时常量池:

Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

      运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中。

使用常量池的好处:

常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。

(1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。

(2)节省运行时间:比较字符串时,==(引用比较地址-基本比较值)比equals()(只能用于引用的比较,比较值)快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。

     举例:

 

 

  • String s1 =”123”;
  • String s2=new String (“123”);

  第一种我们之前已经见过了,这种方式声明的字面量123是在编译期就已经确定的,它会直接进入class文件常量池中;当运行期间在全局字符串常量池中会保存它的一个引用,实际上最终还是要在堆上创建一个”123”对象。
  第二种方式方式使用了new String(),也就是调用了String类的构造函数,我们知道new指令是创建一个类的实例对象并完成加载初始化的,因此这个字符串对象是在运行期才能确定的,创建的字符串对象是在堆内存上
  因此此时调用System.out.println(s0 == s1);返回的肯定是flase,因此==符号比较的是两边元素的地址,s1和s0都存在于堆上,但是地址肯定不相同。

堆内存结构

  • JVM内存划分为堆内存和非堆内存,堆内存分为年轻代(Young Generation)、老年代(Old Generation

年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,

默认比例是8:1:1。

  • 堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。

非堆内存用途:在JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,

他们最大区别是:元空间并不在JVM中,而是使用本地内存。

分代概念

  • 新生成的对象首先放到年轻代Eden区,当Eden空间满了,触发Minor GC,存活下来的对象移动到Survivor0区,Survivor0区满后触发执行Minor GC,Survivor0区存活对象移动到Suvivor1区,这样保证了一段时间内总有一个survivor区为空。经过多次Minor GC仍然存活的对象移动到老年代。
  • 老年代存储长期存活的对象,GC期间会停止所有线程等待GC完成,所以对响应要求高的应用尽量减少发生Major GC,避免响应超时。 Minor GC : 清理年轻代  Major GC : 清理老年代 Full GC : Full GC定义是相对明确的,就是针对整个新生代、老生代、元空间(metaspace,java8以上版本取代perm gen)的全局范围的GC。

什么情况下新生代的对象会被移入老年代?

第1种情况:对象年龄

JVM会给对象增加一个年龄(age)的计数器,对象每“熬过”一次GC,年龄就要+1,待对象到达设置的阈值(默认为15岁)就会被移移动到老年代,可通过-XX:MaxTenuringThreshold调整这个阈值。

 

第2种情况:动态判断

如图上的A、B、D、E这四个对象,假如Survivor 2是100m,如果A + B + D的内存大小超过50m,现在D的年龄是10,那E都会被移动到老年代。实际上这个计算逻辑是这样的:年龄1 + 年龄2 + 年龄n的多个对象总和超过Survivor区的50%,那就会把年龄n以上的对象都放入老年代。

堆内存常用参数

 

动态演示内存使用情况(VirtualVM插件)

 

(GC)垃圾回收器

为啥要做垃圾回收?

如果不进行垃圾回收,内存迟早都会被消耗空,避免内存空间的浪费;空间是很宝贵的,使用有限的空间做最大的事。

怎么找到需要回收的对象?

通过(GC)垃圾回收算法(引用计数器算法、可达性分析算法、标记清除算法、复制算法、标记整理算法、分代收集算法);

引用计数器算法

给对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器加1;当引用失效时,计数器减1。任何时刻计数器为0的对象就是不可能再被使用的。这种算法使用场景很多,

但是,Java中却没有使用这种算法,因为这种算法很难解决对象之间相互引用的情况。(故java虚拟机不使用此类算法)

可达性分析法

通过一系列称为"GC Roots"的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链(即GC Roots到对象不可达时),则证明此对象是不可用的。(java采用此类算法)

GCRoots对象:

(1)虚拟机栈(栈帧中的局部变量区,也叫局部变量表)中引用的对象。

(2)方法区中的类静态变量属性引用的对象。

(3)方法区中常量引用的对象。

(4)本地方法栈中JNI(Native方法)引用的对象。

 

故:上图中的 obj8、obj9、obj10都是不可达的,都是无用对象,当下次GC来的时候会回收此类对象。

可达性算法的4中引用关系:

(1)强引用

(2)弱引用

(3)虚引用

(4)软引用

强引用:

强引用就是指在程序代码之中普遍存在的,类似“Objectobj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

如果不使用时,可以赋值obj=null,显示的设置obj为null,则gc认为该对象不存在引用,这时候就可以回收此对象。

弱引用:

弱引用描述非必需对象。被弱引用关联的对象只能生存到下一次垃圾回收之前,垃圾收集器工作之后,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。Java中的类WeakReference表示弱引用。

软引用:

软引用用来描述一些还有用,但并非必需的对象。对于软引用关联着的对象,如果内存充足,则垃圾回收器不会回收该对象,如果内存不够了,就会回收这些对象的内存。在 JDK 1.2 之后,提供了 SoftReference 类来实现软引用。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。(可以用作图片缓存,但是现在一般使用其他方式来实现缓存,如redis)

虚引用:

如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。

虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃 圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是 否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。(用于JVM内存日志监控)

 

标记清除算法

分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。这种算法的不足主要体现在效率和空间,从效率的角度讲,标记和清除两个过程的的效率都不高;从空间的角度讲,标记清楚后会产生大量不连续的内存碎片,内存碎片太多可能会导致以后程序运行过程中在需要分配较大对象时,无法找到足够的连续内存而不得不提前触发一次垃圾回收动作。

 

复制算法

复制算法是为了解决效率问题而出现的,它将可用的内存分为两块,每次只用其中的一块,当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的内存一次性清理掉。这样每次只需要对整个半区进行内存回收。(缺点1:太消耗内存,10G的内存只能使用5G,当然也可以配置比例,内存资源很宝贵的;缺点2:效率低下,老年代的对象存活率都很高,复制需要额外的时间空间)

 

标记整理算法

为了解决复制算法引起的内存资源浪费以及效率低下问题提出了“标记-整理算法”,与标记-清除算法类似,让所有存活对象都向一端移动,然后清理掉边界以外的内存(可回收的对象)。

 

分代收集算法:(整合上述所有算法优点)

目前虚拟机基本都采用分代收集算法来进行垃圾回收。这种算法结合了以上的内容,根据对象的生命周期的不同将内存划分为几块,然后根据各块的特点采用最适当的收集算法。大批对象死去、少量对象存活的(新生代),使用复制算法,复制成本低;对象存活率高、没有额外空间进行分配担当的(老年代),采用标记-清理算法或者标记-整理算法。

 

垃圾收集器分类

垃圾收集器就是垃圾回收的具体体现。不同虚拟机所提供的垃圾收集器可能会有很大差别,HotSpot垃圾收集器如下图:

 

Serial收集器

采用复制算法的单线程的收集器,单线程一方面意味着他只会使用一个CPU或者一条线程去完成垃圾收集工作,另一方面也意味着他进行垃圾收集时必须暂停其他线程的所有工作。简单而高效。(虚拟机运行在Client模式下的默认新生代收集器)

Parnew收集器

Serial收集器的多线程版本,Server模式下的虚拟机首选的新生代收集器。

Parallel Scavenge收集器

也采用了复制算法,也是并行的多线程收集器。CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。所谓吞吐量就是CPU用于运行用户代码时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。Parallel Scavenge收集器是虚拟机运行在Server模式下的默认垃圾收集器。(吞吐量优先)

Serial Old收集器

Serial收集器的老年代版本,同样是一个单线程收集器,使用==“标记-整理算法”,(虚拟机运行在Client模式下的默认老年代收集器)

Parallel Old收集器

Parallel Scavenge收集器的老年代版本,使用多线程和==“标记-整理算法”==。(吞吐量优先)

CMS收集器

收集器是以获取最短回收停顿时间为目标的收集器。使用标记-清除算法,收集过程分为如下四步:(1)初始标记,标记GCRoots能直接关联到的对象,时间很短。(2)并发标记,进行GCRoots Tracing(可达性分析)过程,时间很长。(3)重新标记,修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,时间较长。(4)并发清除,回收内存空间,时间很长。其中,并发标记与并发清除两个阶段耗时最长,但是可以与用户线程并发执行。

G1收集器

G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。与其他GC收集器相比,G1收集器具有以下特点:
(1)并发和并行。使用多个CPU来缩短Stop The World停顿时间,与用户线程并发执行。

(2)分代收集。独立管理整个堆,但是能够采用不同的方式去处理新创建对象和已经存活了一段时间、熬过多次GC的旧对象,以获取更好的收集效果。

(3)空间整合。基于标记-整理算法,无内存碎片产生。

(4)可预测的停顿。能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集器上的时间不得超过N毫秒。

在G1之前的垃圾收集器,收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分(可以不连续)Region的集合。

解析器和编译器

Java Interpreter(Java 解释器)
Java编译器生成的是与机器码不同的java字节码,并不能被硬件中的CPU直接执行。而java解释器就像植根于软件中的CPU,能够解析并执行java字节码。(字节码解析成汇编指令)

Java Compiler (Java 编译器)
Java编译器读取java源文件(*.java)并将它们编译为java字节码文件(*.class)。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章