JVM基础篇一(内存结构)

目录

一、运行时数据区

二、解析JVM运行时数据区

1.程序计数器(Program Counter Register)

作用:

特点:

2.虚拟机栈(Java Virtual Machine Stacks)

问题辨析

栈内存溢出

3.本地方法栈

4 .堆(Heap)

1.堆内存溢出(java.lang.OutOfMemoryEeror:Java heap space) 

2.堆内存诊断

5.方法区(Method Area)

1方法区内存溢出(java.lang.OutOfMemoryEeror:Metaspace) 

2运行时常量池

3StringTable(串池)

6.直接内存Direct Memory(系统内存) 

分配和回收原理


一、运行时数据区

什么是运行时数据区(就是我们java运行时的东西是放在那里的)

二、解析JVM运行时数据区

1.程序计数器(Program Counter Register)

又称PC寄存器

内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成 

作用:

        是记住下一条jvm指令的执行地址

特点:

  • 内存空间小
  • 线程私有
  • 不会存在内存溢出

2.虚拟机栈(Java Virtual Machine Stacks)

线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表操作数栈动态链接方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。

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

 栈演示:

public class test {

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

    public static void m1(){
        m2(1,2);
    }
    public static int m2(int a,int b){
        int c = a + b;
        return c;
    }
}

 ​​​​​

问题辨析

   垃圾回收是否涉及栈内存?

        不涉及!因为栈帧出栈的时候就已经释放掉了

   栈内存分配越大越好吗?

        不是!因为栈内存太大反而影响到线程数目,采用系统默认的大小即可

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

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

栈内存溢出

原因:

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

3.本地方法栈

  • 本地方法栈很好理解,他很栈很像,只不过方法上带了 native 关键字的栈字
  • 它是虚拟机栈为虚拟机执行Java方法(也就是字节码)的服务
  • native关键字的方法是看不到的,必须要去oracle官网去下载才可以看的到,而且native关键字修饰的大部分源码都是C和C++的代码。
  • 同理可得,本地方法栈中就是C和C++的代码

4 .堆(Heap)

  • java堆是java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。
  • 在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
  • java堆是垃圾收集器管理的主要区域,因此也被成为“GC堆”。
  • 从内存回收角度来看java堆可分为:新生代和老生代。
  • 从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。
  • 无论怎么划分,都与存放内容无关,无论哪个区域,存储的都是对象实例,进一步的划分都是为了更好的回收内存,或者更快的分配内存。
  • 根据Java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中。当前主流的虚拟机都是可扩展的(通过 -Xmx 和 -Xms 控制)。如果堆中没有内存可以完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

定义: 

 Heap堆:通过new关键字创建的对象都会使用堆内存

特点:

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

组成:

堆内存划分:

1)新生区 :Eden、s0(幸存者0区)、s1(幸存者1区)。对象在这里诞生、成长、甚至死亡

Eden:所有对象都是在eden区new出来的。

2)老年区

3)永久区:jdk1.8以后,叫元空间(方法区在这里,常量池在方法区里)。这个区域是常驻内存的,用来存放jdk自身携带的class对象。

1.堆内存溢出(java.lang.OutOfMemoryEeror:Java heap space) 

2.堆内存诊断

jps 工具

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

jmap 工具

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

jconsole 工具

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

5.方法区(Method Area)

定义:

  存储了跟类的结构相关的一些信息,包括类的成员变量、方法数据、成员方法和构造器方法的代码等。

组成:

  • 方法区在虚拟机启动时被创建,逻辑上是堆的组成部分
  • 方法区是规范,永久代(jdk1.6,占用的是堆内存)和元空间(jdk1.8,占用的是系统的内存)只是实现。
  • 注意看元空间里面的包含:类、类加载器、常量池
  • StringTable1.6是放在方法区,1.8则放到了堆空间

1方法区内存溢出(java.lang.OutOfMemoryEeror:Metaspace) 

jdk1.8以前会导致永久代内存溢出:java.lang.OutOfMemoryEeror:PermGen space

需要先设置永久代内存大小:-XX:MaxPermSize=8m

jdk1.8之后会导致元空间内存溢出:java.lang.OutOfMemoryEeror:Metaspace

需要先设置元空间内存大小:XX:MaxMetaspaceSize=8m

2运行时常量池

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

3StringTable(串池)

字符串池在JDK1.7之后存在于堆中的一块区域,String s1 = "abc"这样声明的字符串会放入字符串池中,String s1 = new String("abcd")会在字符串池有一个"abcd"的字符串对象,堆中也有1个,2个不同。

  • 字符串池可以避免重复创建字符串对象
  • 常量池中的字符串仅是符号,第一次用到时才变为对象
  • 它的结构为hash表结构,相同的字符串只存在一份
     

先来看几道面试题如果都知道就不用看StringTable了

public class Test2 {
    //StringTable是一个hashtable结构,不能扩容
    public static void main(String[] args) throws InterruptedException {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b";
        String s4 = s1 + s2;
        String s5 = "ab";
        String s6 = s4.intern();
        // 问
        System.out.println(s3 == s4);
        System.out.println(s3 == s5);
        System.out.println(s3 == s6);
        String x2 = new String("c") + new String("d");
        String x1 = "cd";
        x2.intern();
        // 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
        System.out.println(x1 == x2);
    }
}

StringTable特性:

  • 常量池中的字符串仅是符号,第一次用到时才变为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是 StringBuilder (1.8)
  • 字符串常量拼接的原理是编译期优化
  • 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
    • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串 池中的对象返回
    • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份, 放入串池, 会把串池中的对象返回

StringTable位置 :

  • jdk1.6在永久代(方法区)中,不易被回收
  • jdk1.8之后在堆中,majorGC就可以回收掉,节约空间

StringTable 垃圾回收:

StringTable中存在垃圾回收

案例:往串池中循环加入10万个对象

添加的jvm参数:

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

    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        try {
            for (int j = 0; j < 100000; j++) { // j=100, j=10000
                String.valueOf(j).intern();
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println(i);
        }

    }

可以看到串池中只有7000多个对象,因为因为内存空间不够发生了GC垃圾回收,所有串池中是会存在垃圾回收的

StringTable性能调优:

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

StringTable底层是一个HashTable,因此调优就是调整HashTable桶的个数:
-XX:StringTableSize=10000

总结:当项目中字符串很多时,可以考虑调整StringTable,增加桶的个数,减少Hash碰撞

6.直接内存Direct Memory(系统内存) 

定义:

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

操作系统专门划出来一块内存,供Java直接使用,当然,操作系统也可以使用。

分配和回收原理

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值