Always keep a beginner's mind, |

Jonah_liu

园龄:2年5个月粉丝:0关注:0

📂JVM
🔖jvm
2022-10-15 21:12阅读: 18评论: 0推荐: 0

jvm-1.内存结构

内存结构

1. 程序计数器

1.1 定义

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

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

  • 特点:

    • 是线程私有的

    • 不会存在内存溢出

1.2 作用

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

 

2. 虚拟机栈

2.1 定义

Java Virtual Machine Stacks (Java 虚拟机栈)

  • 每个线程运行时所需要的内存,称为虚拟机栈

  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存

  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

问题辨析

1.垃圾回收是否涉及栈内存

  • 不涉及

2.栈内存分配越大越好吗

  • 不是,由于物理内存一定,栈内存越大会导致线程数变小。栈内存变大了通常只是能进行更多次的方法调用。

3.方法内的局部变量是否线程安全

  • 如果方法内局部变量没有逃离方法的作用范围,它是线程安全的

  • 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全

2.2 栈内存溢出

栈帧过多导致栈内存溢出

栈帧过大导致栈内存溢出

例:方法递归调用未正常结束、循环引用

2.3 线程运行诊断

案例 1 : cpu 占用过多

定位

  • 用top定位哪个进程对cpu的占用过高

  • ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)

  • jstack 进程id

    • 可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号

案例 2 :程序运行很长时间没有结果

  • 可能发生了死锁

 

3. 本地方法栈

为本地方法的调用提供内存空间

 

4. 堆

4.1 定义

Heap 堆

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

特点

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

  • 有垃圾回收机制

4.2 堆内存溢出

当对象一直被创建但又不能被垃圾回收时,可能会导致堆内存溢出

4.3 堆内存诊断

  1. jps 工具 查看当前系统中有哪些 java 进程

  2. jmap 工具 查看堆内存占用情况 :jmap - heap 进程id

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

案例

  • 垃圾回收后,内存占用仍然很高

jvisualym工具:可视化方式展示jvm内容

 

5. 方法区

5.1 定义

JVM规范-方法区定义

方法区是所有java虚拟机线程共享的区域,存储了跟类的结构相关的信息(类的成员变量、方法数据、成员方法、构造器方法)、运行时常量池

方法区在虚拟机启动时被创建,逻辑上是堆的一个组成部分,规范中并不强制方法区的位置

5.2 组成

5.3 方法区内存溢出

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

    • 演示永久代内存溢出 java.lang.OutOfMemoryError: PermGen space

    • -XX:MaxPermSize=8m

  • 1.8 之后会导致元空间内存溢出

    • 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace

    • -XX:MaxMetaspaceSize=8m

类加载过多会导致方法区内存溢出

场景

  • mybatis

  • spring

 

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(); 
//字符串常量池:"a","b","ab"
//堆内存:new String("ab")
// 问
System.out.println(s3 == s4);  //false
System.out.println(s3 == s5);  //true
System.out.println(s3 == s6);  //true
String x2 = new String("c") + new String("d"); //new String("cd") 
String x1 = "cd";
x2.intern();
//字符串常量池:"c","d","cd"
//堆内存:new String("c"),new String(d),new String("cd") 
// 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
System.out.println(x1 == x2);
// StringTable [ "a", "b" ,"ab" ]  hashtable 结构,不能扩容
public class Demo1_22 {
    // 常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
    // ldc #2 会把 a 符号变为 "a" 字符串对象
    // ldc #3 会把 b 符号变为 "b" 字符串对象
    // ldc #4 会把 ab 符号变为 "ab" 字符串对象
​
    public static void main(String[] args) {
        String s1 = "a"; // 懒惰的
        String s2 = "b";
        String s3 = "ab";
        String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString()  new String("ab")
        String s5 = "a" + "b";  // javac 在编译期间的优化,结果已经在编译期确定为ab
​
        System.out.println(s3 == s5);
​
​
​
    }

 

5.5 StringTable 特性(字符串常量池)

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

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

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

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

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

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

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

 

5.6 StringTable 位置

5.7 StringTable 垃圾回收

StringTable在内存不足时会发生垃圾回收

 

5.8 StringTable 性能调优

  • 调整 -XX:StringTableSize=桶个数

    • 串池每次添加新字符串的时候会先查询,如果串池中没用该字符串,才会把它放入串池。由于串池底层是hash表,在我们可以增加桶个数以此降低桶上的链表长度,提高查询的速度,进而加快插入的速度。

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

    • 如果应用里有大量的字符串,而且这些字符串可能会重复,我们可以让字符串入池来减少字符串个数,节约堆内存的使用。

6. 直接内存

 

6.1 定义

Direct Memory

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

6.2 分配和回收原理

使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦
ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调
用 freeMemory 来释放直接内存

This is a offline tool, your data stays locally and is not send to any server!

Feedback & Bug Reports

posted @   Jonah_liu  阅读(18)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起