JVM 虚拟机内存区域

jvm 运行时内存区域?

线程私有的:

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

线程共享的:

  • 方法区
  • 直接内存(非运行时数据区域的一部分)

JDK8 将方法区异常了由元空间取代。

程序计数器

程序计数器简介?

  • 可以看作是当前线程所执行的字节码的行号指示器。字节码解释器通过这个计数器来选取下一条乣执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。
  • 程序计数器是唯一不糊出现OutOfMemoryError异常的区域。

程序计数器的主要作用?

  • 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
  • 在多线程的情况下,记录当前线程执行的位置,方便线程恢复执行后继续执行。

java 虚拟机栈

java虚拟机栈介绍?

java虚拟机栈是线程私有的,它的生命周期和线程相同。它描述的是java方法执行的内存模型,每次方法调用的数据都是通过栈传递的。

栈是由一个个栈帧组成,栈帧中拥有局部变量表、操作数栈、动态链接、方法出口信息

局部变量表主要存放的数据类型?

  • 各种基本数据类型的值。
  • 对象引用(可能是指向对象的指正或者是指向一个代表对象的句柄)。

java 虚拟机栈会抛出哪两种异常?

StackOverFlowError: 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。

OutOfMemoryError:Java 虚拟机栈的内存大小可以动态扩展, 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出 OutOfMemoryError 异常。

java 的方法是如何调用的?

java 虚拟机栈类似数据结构的栈,栈中主要保存的是栈帧,每一次的方法调用都是一个栈帧被压栈的过程,每个方法调用结束,都会有一个栈帧弹出。java 的返回方式只有 return 或者抛出异常,不管哪种,都会导致栈帧弹出。

本地方法栈

本地方法栈简介?

本地方法栈和虚拟机栈非常类似,只是虚拟机栈是为 java 方法调用服务,儿本地方法栈是为虚拟机使用 native 方法服务的。在 Hotspot 中本地方法栈和虚拟机栈是合二为一的。

本地方法执行时,也会在本地方法栈中创建一个栈帧,用以保存本地方法的局部变量表、操作数栈、动态链接、出口信息。

本地方法执行完成后也会出栈。

本地方法调用也会抛出 StackOverFlowError、OutOfMemoryError。

堆内存简介?

堆是Java 虚拟机所管理的内存中最大的一块,堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。

因为 JDK 1.7默认开启的逃逸分析,在方法中引用的对象如果没有其他引用,对象可以直接在栈中创建。

堆也是 java 垃圾收集器管理的主要区域。

逃逸分析?

从 JDK 1.7 开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。

堆内存分区?

堆内存通常被分为:新生代、老年代、永久带。JDK8之后,方法区(Hotspot的永久代)被移除,由元空间取代。

永久代和方法区的关系?

方法区是 jvm 的规范,永久代是Hotspot对方法区的实现。所以说在Hotspot中,方法区(永久代)是堆内存中的。

堆内存中的新生代?

堆内存中的新生代又分为三块, Eden 区、两个 Survivor 区。两个 Survivor 区又叫 from 和 to,或者叫 s0 和s1。在 s0 和s1 中的对象,每经历一次新生代垃圾回收,年龄增加1,当年龄达到15岁,会晋升到老年代中。晋升老年代的年龄可以通过 -XX:MaxTenuringThreshold (最大年龄阈值)设置。

堆内存中会抛出的异常?

对内存中主要抛出 OutOfMemoryError 内存溢出错误。内存异常又分为两种,java.lang.OutOfMemoryError: GC Overhead Limit Exceeded,当gc时间长,且回收内存少时,报这个错误。

java.lang.OutOfMemoryError: Java heap space,当堆内存不足以存放新创建的对象,就会报这个错。可以通过 -Xmx 控制最大堆内存大小,不过这个参数受制于物理内存的大小。

方法区

方法区简介?

方法区也是属于各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、运行时常量池、静态变量、以及编译器编译后的代码等数据。java 虚拟机规范把方法区描述为堆的一个逻辑部分,不过方法区却有一个非堆的别名。

JDK8 之前永久代没有被移除,可以用下面两个参数指定方法区的初始大小和最大大小。

-XX:PermSize=N //方法区 (永久代) 初始大小
-XX:MaxPermSize=N //方法区 (永久代) 最大大小

JDK8 之后可以用下面两个参数设置元空间的初始值和最大值。

-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)
-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小

为什么要用元空间替换永久代呢?

  • 永久代会使用 JVM 的固定内存空间,容易发生溢出。而元空间直接使用直接内存,不受 JVM 固定内存空间控制。元空间溢出抛出 java.lang.OutOfMemoryError: MetaSpace.
  • 在 JDK8中,合并 Hotspot 和 JRockit 时,JRockit 从来都没有叫永久代的地方,所以也就没有必要设置永久代了。

方法区的运行时常量池?

运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池表(用于存放编译期生成的各种字面量和符号引用)。

运行时常量池也会抛出 OutOfMemoryError 错误。

JDK7之前,运行时常量池包含字符串常量池,存放在方法区,此时 hotspot 虚拟机对方法区的实现为永久代。

JDK7 字符串常量池放到了堆中,运行时常量池还在方法区中。

JDK8 移除了方法区,则运行时常量池随着类文件元信息移到了元空间。

直接内存?

直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。

JDK1.4 中新加入的 NIO,使用 Native 函数库直接分配堆外内存,然后通过 DirectByteBuffer 对象作为这块内存的引用进行操作,避免在 Java 堆和 Native 堆之间来回复制数据,从而提高性能。

本机直接内存的分配不会受到 Java 堆的限制,但是会受到物理机总内存大小以及处理器寻址空间的限制。

java 对象创建

对象创建过程?

  • 类加载检查
  • 分配内存
  • 初始化零值
  • 设置对象头
  • 执行init方法

类加载检查过程?

虚拟机遇到 new 指令时,首先检查常量池中是否有这类的符号引用,并检查这个符号引用代表的类是否已经被加载、解析、初始化过,如果没有,先执行内加载过程。

分配内存?

在类加载检查通过后,便可以确定对象的所需要内存大小,接下来虚拟机将在堆内存中,为对象分配一块确定大小的内存空间。

内存分配的方式?

内存分配方式有指针碰撞空闲列表两种,选择哪种分配方式由堆内存是否规整决定,堆内存是否规整又由于所采用的垃圾收集器是否带有压缩功能决定。收集器(CMS)。

当使用标记-清楚算法时,内存不规整,会使用空闲列表法,空闲列表记录可以使用的内存空间,进行分配。当使用标记-整理算法时,内存规整,会使用指针碰撞法,指针只需要沿着没有用过的内存区域移动对象大小的位置即可。收集器(Serial、ParNew)。

初始化零值?

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

设置对象头?

初始化零值后,虚拟机会对对象头进行必要的设置,比如对象的类信息,哈希码,对象的分代年龄,以及是否启用偏向锁等。

执行init方法?

在完成设置对象头后,从虚拟机的视角看,对象已经创建完成。但是从java程序视角看,对象创建才开始,执行 init 方法,将所有的零值设置为初始值。对象才算初始化完成。

对象的访问定位?

java 程序通过栈上的引用来操作堆上的对象。对对象的访问方式由虚拟机实现而定,一般有使用句柄直接指针两种:

使用句柄:需要在堆中开辟一块空间存储句柄,栈中的引用指向句柄的地址,句柄指向对象和对象在方法区中的类信息。

使用指针:栈中的引用直接指向堆中的对象,由堆类考虑如何分配内存地址,方便通过对象定位到对象在方法区中的类信息。

字符串常量池相关

  • 直接拼接和引用拼接
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing"; //常量池中的对象(编译器优化)
String str4 = str1 + str2;   //在堆上创建的新的对象 (实际上是StringBuilder调用append方法和toString方法实现的)
String str5 = "string";      //常量池中的对象
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
  • 在编译器有确切的值,可以被优化
final String str1 = "str";
final String str2 = "ing";
// 下面两个表达式其实是等价的
String c = "str" + "ing";// 常量池中的对象
String d = str1 + str2; // 常量池中的对象
System.out.println(c == d);// true
final String str1 = "str";
final String str2 = getStr();
String c = "str" + "ing";// 常量池中的对象
String d = str1 + str2; // 在堆上创建的新的对象
System.out.println(c == d);// false
public static String getStr() {
  return "ing";
}

String str2 = new String("abcd"); 创建步骤?会创建几个对象?

  • 在堆中创建一个字符串对象。
  • 检查字符串常量池中是否有和 new 的字符串值相等的字符串常量
  • 如果没有的话,需要在字符串常量池中也创建一个值相等的字符串常量。如果有的话,就直接返回堆中的字符串实例对象地址

String 的 intern 方法?

  • 直接使用双引号声明出来的 String 对象会直接存储在常量池中。

  • 使用 String 提供的 intern() 方法也有同样的效果,如果常量池中存这个对象,直接返回常量池中该对象的引用。如果不存在:

    • JDK7 之前,会在常量池创建相同的对象,并返回常量池的字符串的引用。
    • JDK7 之后,是直接将堆中对象的引用地址放到常量池中,减少不必要的内存开销。
    String s1 = "Javatpoint";
    String s2 = s1.intern(); // 在常量池中存在,直接返回常量池的引用
    String s3 = new String("Javatpoint"); // 新建堆上对象,返回堆上对象的引用
    String s4 = s3.intern(); // 在常量池中存在,直接返回常量池的引用
    System.out.println(s1==s2); // True
    System.out.println(s1==s3); // False
    System.out.println(s1==s4); // True
    System.out.println(s2==s3); // False
    System.out.println(s2==s4); // True
    System.out.println(s3==s4); // False
    

8中基本类型的包装类和常量池技术?

Byte, Short, Integer, Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True Or False。

两种浮点数类型的包装类 Float, Double 并没有实现常量池技术。

Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// 输出 true
Integer i11 = 333;
Integer i22 = 333;
System.out.println(i11 == i22);// 输出 false
Double i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);// 输出 false

自动装箱会创建新对象,导致 == 比较返回 false。

Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2);

包装类型进行计算会自动拆箱,使用 == 比较返回 true。

Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);
System.out.println(i4 == i5);// false
System.out.println(i4 == i5 + i6);// true
System.out.println(40 == i5 + i6);// true
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,458评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,030评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,879评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,278评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,296评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,019评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,633评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,541评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,068评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,181评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,318评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,991评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,670评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,183评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,302评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,655评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,327评论 2 358

推荐阅读更多精彩内容