jvm内存模型总结

参考javaguide(guide哥的原文链接)

JVM内存模型基础和常见面试题总结

说说运行是基本数据区域?

哪些是线程私有的?

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

那些是线程共享的?

  • 方法区
  • 直接内存

image-20211022003550660

程序计数器的作用?

  • 记录线程执行的位置
  • 负责指向下一条线程需要执行的字节码
  • 不会出现OutMemoryError

java虚拟机栈的作用?

  • 负责存入线程处理的方法栈帧
  • 各种局部变量

本地方法栈?

  • 执行native方法

堆?

  • 最大的内存区域,存放对象
  • 划分为老年代、新生代(幸存区、伊甸区)
  • jdk8之后消除了永久代,变成元空间
  • 对象一开始存在于伊甸区,每次进入幸存区那么年龄就是+1,如果达到阈值那么就会进入老年区,阈值可以通过-XX:MaxTenuringThreshold设置,如果幸存区的某个年龄大小超过幸存区的一半,那么就会以这个年龄或者是MaxTenuringThreshold的最小值作为阈值

堆中出现不同的溢出问题?

  • java.lang.OutOfMemoryError: GC Overhead Limit Exceeded其实就是jvm垃圾回收次数多,但是每次都没怎么回收到东西
  • java.lang.OutOfMemoryError: Java heap space堆内存溢出,创建对象太多

image-20211022004432657

方法区的作用?

  • 存储各种类信息、常量和静态变量还有即时编译器的代码优化
  • 非堆
  • 方法区其实就是一种规范,永久代和元空间就是方法区的是实现方式

为什么后期使用元空间?

  • 永久代是存在于java内存,那么这个时候是受jvm控制大小,容易发生溢出问题
  • 但是元空间是系统内存,相对出现内存溢出的机会很小,元空间的系统内存大小可以装下更多的类元信息

运行时常量池?

  • class文件不仅仅有类的元信息(接口,属性,版本,字段)还有常量池表
  • 用于存字面量和符号引用
  • 可以通过intern()把变量放入常量池

字符串常量池的位置?

  • jdk1.7以前存放到方法区,也就是永久代里面(hotspot)
  • jdk1.7拿到了堆,其它放到了方法区(hotspot)
  • jdk1.8就是把方法区通过元空间来实现,字符串常量池在堆,运行时常量池在方法区

直接内存的作用?

  • 引入了NIO类,实现了系统和java堆的一个通道,和缓存区的IO实现,native库能直接分配堆外内存,并且通过把信息传输到堆的DirectByteBuffer作为引用操作,相当于就是系统和java堆的中间缓存减少系统和java堆的来回复制

HotSpot如何创建对象?

对象的创建

1.类加载检查

  • 虚拟机遇到一条new指令,首先检查指令能否定位到常量池的这个类的符号引用,并且检查这个符号引用代表的类是否引用过、加载过、初始化过。如果没有就要进行类加载
  • 实际上就是检查new的这个类是否加载,没有加载就通过类加载器进行加载

2.分配内存

  • 类加载检查之后就是分配内存
  • 分配方式有指针碰撞和空闲列表
  1. 指针碰撞适用场景是堆内存规整,用过的内存和没用过的分开,中间一个分界值指针,指针移动到一个对象的空间就可以了。而且是使用算法标记整理。(SerialOld,ParNew)
  2. 空闲类表通过表来记录空闲内存,用于内存不规整的情况就是标记清除(CMS)
  • 内存分配处理并发问题,通过CAS+重复尝试,如果TLAB(jvm给线程分配的内存)不够的话,那么就再使用CAS+重复尝试来分配新的内存。

为什么会产生并发问题?

  • 因为程序多个线程运行可能会导致线程争夺空间。

3.初始化零值

  • 分配内存之后初始化对象的属性赋0值

4.设置对象头

设置对象头,实际上就是计算对象hashCode、gc分代年龄、类信息

5.执行init方法

按照程序员的意愿初始化对象,调用构造函数

检查->分配内存->初始化零值->设置对象头->执行init方法

image-20211022010630268

对象的内存布局?

  • 对象头(基本信息,或者是指向monitor信息、锁情况)
  1. 如果是数组还需要记录数组的长度
  • 实例数据(有效数据)
  1. 可以通过-XX:FieldsAllocationStyle来规定变量的分配顺序
  • 对齐填充(8字节倍数)

对象的访问位置

  • 句柄:java堆中划分一片区域来存句柄池,主要指向对象实例数据和对象类型数据地址。
  1. 好处就是能够有稳定的句柄池来指向对象实例和对象数据,保证reference不会被回收

image-20211022013244841

  • 直接指针:指向对象数据,对象里面又有指向对象类型数据地址
  1. 如果不访问类的信息,就能节省一次访问时间
  2. 速度更快

image-20211022013233254

常量池中常考问题?

String 类型的变量和常量做“+”运算时发生了什么

  • 实际上就是JIT把他们拼接到了一起。所以最后的str5和str3是相同的。因为他们最后都是放进了常量池
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";//常量池中的对象
String str4 = str1 + str2; //在堆上创建的新的对象
String str5 = "string";//常量池中的对象
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false

对于编译期中可以确定的String?

  • jvm会把他们直接存入字符串常量池

常量池的作用?

避免字符串重复创建

什么是常量折叠?

  • 就是jvm在编译期帮你把能够拼接和直接显示的字符或者常量存入常量池

final修饰

  • Sring s4=str1+str2其实相当于就是new StringBuilder().append(str1).append(str2).toString();在堆中创建对象而不是引用常量池的对象
  • final修饰可以看成就是一个常量。所以相加得到的还是常量池的对象
  • 下面之所以会出现错误是因为方法必须是在编译之后才能够被解释,也就是说,d和c其实都是在编译之后才能够知道str2长什么样,那么这个时候就只能够在堆上面创建对象c和对象d,并且检查常量池是否存在string,如果不存在那么就再创建一个相等的字符串常量放入常量池,如果有那么就直接返回字符串对象在堆中的地址
final String str1 = "str";
final String str2 = "ing";
// 下面两个表达式其实是等价的
String c = "str" + str2;// 常量池中的对象
String d = str1 + str2; // 常量池中的对象
System.out.println(c == d);// true

image-20211022013934603

new String做了什么事?

  • 创建字符串对象在堆
  • 接着就是检查字符串常量池是否存在对象
  • 如果没有那么就创建一个在常量池,如果有就直接返回对象的实例
String str2 = new String("abcd");
String str3 = new String("abcd");

实践部分

堆溢出

  • 堆溢出只需要不断给容器创建对象就可以

虚拟机栈溢出

  • 最危险的是不断创建线程导致os假死问题(这种是申请内存不足,而且由于线程太多,os处理不过来)
  • 还有一种可能就是变量太多或者执行方法导致栈帧太多,线程栈溢出

方法区常量池溢出

  • 可以通过不断intern来放入常量去把它挤满
  • 出现一个问题

下面代码在jdk6和7分别是什么答案?

  • jdk6全部false原因是创建字符串对象先看看常量池有没有,如果没有复制对象过去,并且返回常量池对象的引用很明显就是和堆的不一样(永久代)
  • jdk7没有复制,字符串常量池已经搬到堆中,那么只需要查看常量池有没有,有就返回实例对象的引用,没有就创建一个并返回常量池的引用。
public class MyTest {
    public static void main(String[] args) {
        String str1=new StringBuilder("计算机").append("软件").toString();
        System.out.println(str1.intern()==str1);
        String str2=new StringBuilder("ja").append("va").toString();
        System.out.println(str2.intern()==str2);
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值