JVM学习笔记(二) 内存结构

1.程序计数器

1.1 定义

Program Counter Register 程序计数器(寄存器)
作用:是记录下一条 jvm 指令的执行地址行号。
特点:

  • 是线程私有的
  • 不会存在内存溢出

1.2 作用


运行流程:

  • JVM指令 --> 解释器解释 --> 翻译成机器码 -->CPU运行
  • 程序计数器会记住下一条jvm指令的执行地址。当cpu运行完后,解释器会去程序计数器中拿到行号指向下一条指令进行解释。

多线程环境:

  • 多线程的环境下,如果两个线程发生了上下文切换,那么程序计数器会记录线程下一行指令的地址行号,以便于接着往下执行。

2.虚拟机栈

2.1 定义

Java Virtual Machine Stacks (Java 虚拟机栈)

  • 每个线程运行需要的内存空间,称为虚拟机栈
  • 每个栈由多个栈帧(Frame)组成,对应着每次调用方法时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的方法

问题辨析:
垃圾回收是否涉及栈内存?
不会。栈内存是方法调用产生的,方法调用结束后会弹出栈。
栈内存分配越大越好吗?
不是。因为物理内存是一定的,栈内存越大,可以支持更多的递归调用,但是可执行的线程数就会越少。
方法内的局部变量是否线程安全?

  • 如果方法内的局部变量没有逃离方法的作用范围,它是线程安全的
  • 如果是局部变量引用了对象,并逃离了方法的作用范围,那就要考虑线程安全问题

2.2 栈内存溢出

  • 栈帧过多导致栈内存溢出
  • 栈帧过大导致栈内存溢出

3.本地方法栈

一些带有 native 关键字的方法就是需要 JAVA 去调用本地的C或者C++方法,因为 JAVA 有时候没法直接和操作系统底层交互,所以需要用到本地方法栈,服务于带 native 关键字的方法。

4.堆

4.1 定义

Heap 堆
通过new关键字创建的对象都会被放在堆内存
特点

  • 它是线程共享,堆内存中的对象都需要考虑线程安全问题
  • 有垃圾回收机制

4.2 堆内存溢出

java.lang.OutofMemoryError :java heap space. 堆内存溢出
可以使用 -Xmx8m 来指定堆内存大小。

4.3 堆内存诊断

  • jps 工具
    • 查看当前系统中有哪些 java 进程
  • jmap 工具
    • 查看堆内存占用情况 jmap - heap 进程id
  • jconsole 工具
    • 图形界面的,多功能的监测工具,可以连续监测

5.方法区

5.1 定义

与Java堆一样,是一块独立的空间,也是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

5.2 组成

5.3 方法区内存溢出

  • 1.8 之前会导致永久代内存溢出
    • 使用 -XX:MaxPermSize=8m 指定永久代内存大小
  • 1.8 之后会导致元空间内存溢出
    • 使用 -XX:MaxMetaspaceSize=8m 指定元空间大小

5.4 运行时常量池

首先看看常量池是什么,编译如下代码:

//二进制字节码包含(类的基本信息,常量池,类方法定义,包含了虚拟机的指令)
public class HelloWorld {
    public static void main(String[] args){
        System.out.println("Hello World");
    }
}

然后使用 javap -v Test.class 命令反编译查看

在这里插入图片描述

image.png
常量池:
就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量 等信息
运行时常量池:
常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量 池,并把里面的符号地址变为真实地址

5.5 StringTable

  • 常量池中的字符串仅是符号,只有在被用到时才会转化为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是StringBuilder
  • 字符串常量拼接的原理是编译器优化
  • 可以使用intern方法,主动将串池中还没有的字符串对象放入串池中

intern方法 1.8
调用字符串对象的 intern 方法,会将该字符串对象尝试放入到串池中

  • 如果串池中没有该字符串对象,则放入成功
  • 如果有该字符串对象,则放入失败
    无论放入是否成功,都会返回串池中的字符串对象

例一:

	public static void main(String[] args) {
		// "a" "b" 被放入串池中
		String s = new String("a") + new String("b");
        // 堆  new String("a") new String("b") new String("ab") 
        
         /**
         调用intern方法
       	 这时串池中没有"ab",会将该字符串对象放入到串池中
         此时堆内存与串池中的 "ab" 是同一个对象
        **/
		String s2 = s.intern();
       
		// 因为堆内存与串池中的 "ab" 是同一个对象,所以以下两条语句打印的都为 true
		System.out.println(s2 == "ab" );
		System.out.println(s == "ab");
	}

例二:

public static void main(String[] args){
        // "ab" "a" "b" 被放入串池中
        String x = "ab";
        String s = new String("a") + new String("b");
        // 堆  new String("a") new String("b") new String("ab")

        /**
         调用intern方法
         这时串池中有"ab",不会将该字符串对象放入到串池中
         此时堆内存与串池中的 "ab" 不是同一个对象
         s2:返回的串池中的对象ab
         s:堆中的对象ab
         **/
        String s2 = s.intern();
        //  true   false
        System.out.println(s2 == "ab" );
        System.out.println(s == "ab");
    }

5.6 StringTable 的位置

jdk1.6 StringTable 位置是在永久代中。
jdk1.8 StringTable 位置是在堆中。

5.7 StringTable 垃圾回收

StringTable中存储的虽然是字符串常量,依旧会被垃圾回收

5.8 StringTable 性能调优

  • 调整 -XX:StringTableSize=桶个数
  • 考虑将字符串对象是否入池

6.直接内存

6.1定义

Direct Memory

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

6.2 使用直接内存的好处

文件读写流程:
因为 java 不能直接操作文件管理,需要切换到内核态,调用本地方法进行操作,然后读取磁盘文件,会在系统内存中创建一个缓冲区,将数据读到系统缓冲区, 然后在将系统缓冲区数据复制到 java 堆内存中。缺点是数据存储了两份,在系统内存中有一份,java 堆中有一份。
image.png
使用了 DirectBuffer 文件读取流程
操作系统和 Java 代码都可以直接访问的一块区域,从而提高了效率。
image.png

6.3 直接内存的回收原理

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

//	一般做JVM调优,会加上如下代码
-XX:+DisableExplicitGC  // 静止显示的GC,使下面代码失效

System.gc();	//显式的垃圾回收(Full GC),会回收新生代和老年代,使程序执行时间较长

根据黑马程序员JVM课程,编写笔记
https://www.bilibili.com/video/BV1yE411Z7AP?p=1&share_source=copy_web

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值