JVM----内存结构

Java Virtual Machine - java 程序的运行环境(java 二进制字节码的运行环境)

JVM----内存结构

一、内存结构

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈
  • 方法区

1. 程序计数器

1.1 定义

Program Counter Register 程序计数器(寄存器)

1.2 作用

        二进制字节码       jvm指令         java源代码
		0: getstatic      #20        // PrintStream out = System.out;
        3: astore_1                  // --
        4: aload_1                   // out.println(1);
        5: iconst_1                  // --
        6: invokevirtual  #26        // --
        9: aload_1                   // out.println(2);
        10: iconst_2                 // --
        11: invokevirtual #26        // --
        14: aload_1                  // out.println(3);
        15: iconst_3                 // --
        16: invokevirtual #26        // --
        19: aload_1                  // out.println(4);
        20: iconst_4                 // --
        21: invokevirtual #26        // --
        24: aload_1                  // out.println(5);
        25: iconst_5                 // --
        26: invokevirtual #26        // --
        29: return

拿到指令 --> 解释器转为机器码 --> 机器码给CPU

  • 作用:是记住下一条jvm指令的执行地址

  • 特点:

    1. 是线程私有的

    ​ 假如现在有一本书,有好几个同学都想看,我们采取这样的策略让所有同学都能看到:每个人看一天,不管看没看完都要交给下一个人看,不断循环,直到所有人看完。每个同学都有一个小卡片记录自己看到了哪里,这样下次轮到自己看的时候就能快速的接着上次看到的地方继续看。

    ​ Java虚拟机中多线程采用时间片轮转的方式实现,一个处理器(如果是多核处理器就是一个内核)同一时间只能被一个线程使用,同一时间只能执行一个线程的指令,当时间片用完,处理器就要交给别的线程使用,为了下一次轮到自己使用处理器是能够接着执行现在的指令,使用一个计数器来记录。

    1. 不会存在内存溢出

2. 虚拟机栈

JVM----内存结构

2.1 定义

Java Virtual Machine Stacks (Java 虚拟机栈)

  • 每个线程运行时所需要的内存,称为虚拟机栈
  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
    • 栈帧参数、局部变量、返回地址
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
演示栈帧
public class Demo1_1 {

    public static void main(String[] args) {
        method1();
    }

    private static void method1() {
        method2(1, 2);
    }

    private static int method2(int a, int b) {
        int c = a + b;
        return c;
    }
}

JVM----内存结构

问题辨析

  1. 垃圾回收是否涉及栈内存?不需要垃圾回收栈内存。
  2. 栈内存分配越大越好吗?不是,分配的越大,可以更多次的递归方法调用,但会使线程的数目变少。(一般使用系统默认大小)
  3. 方法内的局部变量是否线程安全?是,每一个线程会有自己的一个栈帧,局部变量互不影响。如果是static成员变量、成员变量或者形参,其他线程可能会访问到他,则会产生影响,线程不安全
  • 如果方法内局部变量没有逃离方法的作用访问,它是线程安全的
  • 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全

2.2 栈内存溢出

java.lang.*Error

  • 栈帧过多导致栈内存溢出(递归、第三方库)
    • -Xss:设置线程栈的大小
  • 栈帧过大导致栈内存溢出

2.3 线程运行诊断

案例1: cpu 占用过多

定位

  • top命令定位哪个进程对cpu的占用过高
  • ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
  • jstack 进程id
    • 可以根据线程id 找到有问题的线程(进程id为10进制,jstack为16进制),进一步定位到问题代码的源码行号
案例2:程序运行很长时间没有结果

3. 本地方法栈

本地方法栈的功能和特点类似于虚拟机栈,均具有线程隔离的特点以及都能抛出*Error和

OutOfMemoryError异常。

不同的是,本地方法栈服务的对象是JVM执行的native方法,而虚拟机栈服务的是JVM执行的java方法。

JVM----内存结构

4. 堆

JVM----内存结构

4.1 定义

Heap 堆

  • 通过 new 关键字,创建对象都会使用堆内存

    特点

  • 它是线程共享的,堆中对象都需要考虑线程安全的问题

  • 有垃圾回收机制

4.2 堆内存溢出 ( -Xmx8m 堆最大值)

java.lang.OutOfMemoryError: Java heap space

4.3 堆内存诊断

  1. jps 工具

    查看当前系统中有哪些 java 进程

  2. jmap 工具

    查看堆内存占用情况 jmap - heap 进程id

  3. jconsole 工具

    图形界面的,多功能的监测工具,可以连续监测

更好用的(堆转储dump):jvisualvm

5. 方法区

JVM----内存结构

1.8之前 永久代 使用堆内存

1.8之后 元空间 使用操作系统空间

5.1 定义

5.2 组成

JVM----内存结构

5.3 方法区内存溢出

  • 1.8 以前会导致永久代内存溢出

    * 演示永久代内存溢出 java.lang.OutOfMemoryError: PermGen space
    * -XX:MaxPermSize=8m
    
  • 1.8 之后会导致元空间内存溢出

    * 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace
    * -XX:MaxMetaspaceSize=8m  
    

    场景

  • spring

  • mybatis

5.4 运行时常量池

  • 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量

    等信息

  • 运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量

    池,并把里面的符号地址变为真实地址

5.5 StringTable

先看几道面试题:

String s1 = "a";
String s2 = "b"; 
String s3 = "a" + "b";  //常量池:ab
String s4 = s1 + s2;  // 堆:new String("ab");
String s5 = "ab";
String s6 = s4.intern();    // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 都会把串池中的对象返回
// https://zhuanlan.zhihu.com/p/55468381   intern()
// 问
System.out.println(s3 == s4);   // false s3是在常量池中的,s4是在堆中的
System.out.println(s3 == s5);   // true  都在常量池中
System.out.println(s3 == s6);   // true  都在常量池中
String x2 = new String("c") + new String("d");  
String x1 = "cd";  
x2.intern();  // 没有做任何操作,如果取返回值比较,则为true
// 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢 // true,在常量cd进入常量池之前创建cd常量
// 1.6 的话,则会复制一份放入常量池,x2 == x1 false
System.out.println(x1 == x2);  // false

5.6 StringTable 特性

  • 常量池中的字符串仅是符号,第一次用到时才变为对象

  • 利用串池的机制,来避免重复创建字符串对象

  • 字符串变量拼接的原理是 StringBuilder (1.8)

  • 字符串常量拼接的原理是编译期优化

  • 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池

    • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串

      池中的对象返回

    • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,
      放入串池, 会把串池中的对象返回

5.7 StringTable 位置

 * 演示 StringTable 位置
 * 在jdk8下设置 -Xmx10m -XX:-UseGCOverheadLimit  //  在堆空间下
 * 在jdk6下设置 -XX:MaxPermSize=10m   //  在永久代里面

5.8 StringTable 垃圾回收

-Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc

5.9 StringTable 性能调优

  • 调整 -XX:StringTableSize=桶个数

  • 考虑将字符串对象是否入池

6. 直接内存

操作系统的内存

6.1 定义

Direct Memory

  • 常见于 NIO 操作时,用于数据缓冲区
  • 分配回收成本较高,但读写性能高
  • 不受 JVM 内存回收管理

6.2 分配和回收原理

  • 使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法

  • ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦

    ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调

    用 freeMemory 来释放直接内存

-XX:+DisableExplicitGC 禁用显式的调用System.gc();

此时需要释放直接内存时,则需要unsafe来直接管理内存

上一篇:Silverlight技术调查(2)——跨域访问


下一篇:javascript中的defer和async学习+javascript执行顺序