经常有人会有这么一个疑惑,难道 Java 开发就一定要懂得 JVM 的原理吗?我不懂 JVM ,但我照样可以开发。确实,但如果懂得了 JVM ,可以让你在技术的这条路上走的更远一些。

JVM 的重要性

首先你应该知道,运行一个 Java 应用程序,我们必须要先安装 JDK 或者 JRE 。这是因为 Java 应用在编译后会变成字节码,然后通过字节码运行在 JVM 中,而 JVM 是 JRE 的核心组成部分。

优点

JVM 不仅承担了 Java 字节码的分析(JIT compiler)和执行(Runtime),同时也内置了自动内存分配管理机制。这个机制可以大大降低手动分配回收机制可能带来的内存泄露和内存溢出风险,使 Java 开发人员不需要关注每个对象的内存分配以及回收,从而更专注于业务本身。

缺点

这个机制在提升 Java 开发效率的同时,也容易使 Java 开发人员过度依赖于自动化,弱化对内存的管理能力,这样系统就很容易发生 JVM 的堆内存异常、垃圾回收(GC)的不合适以及 GC 次数过于频繁等问题,这些都将直接影响到应用服务的性能。

内存模型

JVM 内存模型共分为5个区:堆(Heap)、方法区(Method Area)、程序计数器(Program Counter Register)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)。

其中,堆(Heap)、方法区(Method Area)为线程共享,程序计数器(Program Counter Register)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)为线程隔离。

堆(Heap)

堆是 JVM 内存中最大的一块内存空间,该内存被所有线程共享,几乎所有对象和数组都被分配到了堆内存中。

堆被划分为新生代和老年代,新生代又被进一步划分为 Eden 区和 Survivor 区,最后 Survivor 由 From Survivor 和 To Survivor 组成。

随着 Java 版本的更新,其内容又有了一些新的变化:

在 Java6 版本中,永久代在非堆内存区;到了 Java7 版本,永久代的静态变量和运行时常量池被合并到了堆中;而到了 Java8,永久代被元空间(处于本地内存)取代了。

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

为了融合 HotSpot JVM 与 JRockit VM,因为 JRockit 没有永久代,所以不需要配置永久代。

永久代内存经常不够用或发生内存溢出(应该是 JVM 中占用内存最大的一块),产生异常 java.lang.OutOfMemoryError: PermGen。在 JDK1.7 版本中,指定的 PermGen 区大小为 8M,由于 PermGen 中类的元数据信息在每次 FullGC 的时候都可能被收集,回收率都偏低,成绩很难令人满意;还有,为 PermGen 分配多大的空间很难确定,PermSize 的大小依赖于很多因素,比如,JVM 加载的 class 总数、常量池的大小和方法的大小等。

看到这儿,自然就想到了 GC 回收算法,不用急,我会在之后的文章中进行讲解,现在还是以 JVM 内存模型为主。

方法区(Method Area)

什么是方法区?

方法区主要是用来存放已被虚拟机加载的类相关信息,包括类信息、常量池(字符串常量池以及所有基本类型都有其相应的常量池)、运行时常量池。这其中,类信息又包括了类的版本、字段、方法、接口和父类等信息。

类信息

JVM 在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。

在加载类的时候,JVM 会先加载 class 文件,而在 class 文件中便有类的版本、字段、方法和接口等描述信息,这就是类信息。

常量池

在 class 文件中,除了类信息,还有一项信息是常量池 (Constant Pool Table),用于存放编译期间生成的各种字面量和符号引用。

那字面量和符号引用又是什么呢?

字面量包括字符串(String a=“b”)、基本类型的常量(final 修饰的变量),符号引用则包括类和方法的全限定名(例如 String 这个类,它的全限定名就是 Java/lang/String)、字段的名称和描述符以及方法的名称和描述符。

运行时常量池

当类加载到内存后,JVM 就会将 class 文件常量池中的内容存放到运行时常量池中;在解析阶段,JVM 会把符号引用替换为直接引用(对象的索引值)。

例如:

类中的一个字符串常量在 class 文件中时,存放在 class 文件常量池中的。

在 JVM 加载完类之后,JVM 会将这个字符串常量放到运行时常量池中,并在解析阶段,指定该字符串对象的索引值。

运行时常量池是全局共享的,多个类共用一个运行时常量池,因此,class 文件中常量池多个相同的字符串在运行时常量池只会存在一份。

讲到这里,大家是不是有些头晕了,说实话,我在看到这些内容的时候,也是云里雾里的,这里举个例子帮助大家理解:

public static void main(String[] args) {

String str = "Hello";

System.out.println((str == ("Hel" + "lo")));

String loStr = "lo";

System.out.println((str == ("Hel" + loStr)));

System.out.println(str == ("Hel" + loStr).intern());

}

其运行结果为:

true

false

true

第一个为 true,是因为在编译成 class 文件时,能够识别为同一字符串的, JVM 会将其自动优化成字符串常量,引用自同一 String 对象。

第二个为 false,是因为在运行时创建的字符串具有独立的内存地址,所以不引用自同一 String 对象。

最后一个为 true,是因为 String 的 intern() 方法会查找在常量池中是否存在一个相等(调用 equals() 方法结果相等)的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。

涉及到的Error

OutOfMemoryError出现在方法区无法满足内存分配需求的时候,比如一直往常量池中加入数据,运行时常量池就会溢出,从而报错。

程序计数器(Program Counter Register)

程序计数器是一块很小的内存空间,主要用来记录各个线程执行的字节码的地址,例如,分支、循环、跳转、异常、线程恢复等都依赖于计数器。

由于 Java 是多线程语言,当执行的线程数量超过 CPU 数量时,线程之间会根据时间片轮询争夺 CPU 资源。如果一个线程的时间片用完了,或者是其它原因导致这个线程的 CPU 资源被提前抢夺,那么这个退出的线程就需要单独的一个程序计数器,来记录下一条运行的指令。

由此可见,程序计数器和上下文切换有关。

虚拟机栈(VM Stack)

虚拟机栈是线程私有的内存空间,它和 Java 线程一起创建。

当创建一个线程时,会在虚拟机栈中申请一个线程栈,用来保存方法的局部变量、操作数栈、动态链接方法和返回地址等信息,并参与方法的调用和返回。

每一个方法的调用都伴随着栈帧的入栈操作,方法的返回则是栈帧的出栈操作。

可以这么理解,虚拟机栈针对当前 Java 应用中所有线程,都有一个其相应的线程栈,每一个线程栈都互相独立、互不影响,里面存储了该线程中独有的信息。

涉及到的Error

StackOverflowError出现在栈内存设置成固定值的时候,当程序执行需要的栈内存超过设定的固定值时会抛出这个错误。

OutOfMemoryError出现在栈内存设置成动态增长的时候,当JVM尝试申请的内存大小超过了其可用内存时会抛出这个错误。

本地方法栈(Native Method Stack)

本地方法栈跟虚拟机栈的功能类似,虚拟机栈用于管理 Java 方法的调用,而本地方法栈则用于管理本地方法的调用。

但本地方法并不是用 Java 实现的,而是由 C 语言实现的。

也就是说,本地方法栈中并没有我们写的代码逻辑,其由native修饰,由 C 语言实现。

总结

以上就是 JVM 内存模型的基本介绍,大致了解了一下5个分区及其相应的含义和功能,由此可以继续延伸出 Java 内存模型、 GC 算法等等,我也会在之后的文章中进行讲解。如果你有什么想法,欢迎在下方留言。

有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

java 内存模型面试_Java面试- JVM 内存模型讲解相关推荐

  1. JVM(Java虚拟机)详解(JVM 内存模型、堆、GC、直接内存、性能调优)

    JVM(Java虚拟机) JVM 内存模型 结构图 jdk1.8 结构图(极简) jdk1.8 结构图(简单) JVM(Java虚拟机): 是一个抽象的计算模型. 如同一台真实的机器,它有自己的指令集 ...

  2. jvm内存结构_浅谈JVM内存结构

    JVM 可以分为 5 个部分,分别是: 类加载器(Class Loader):加载字节码文件到内存. 运行时数据区(Runtime Data Area):JVM 核心内存空间结构模型. 执行引擎(Ex ...

  3. 图解JVM内存三大核心区域及其JVM内存案例实战剖析

    2019独角兽企业重金招聘Python工程师标准>>> 1 图解JVM内存三大核心区域 2 JVM内存使用案例剖析 public class HelloJVM {/*** JVM 运 ...

  4. 【BAT面试】JDK8 JVM内存模型

    内存是非常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载着操作系统和应用程序的实时运行 JVM内存布局规定了Java在运行过程中内存申请.分配.管理的策略,保证了JVM的高效稳定运行 不同的JV ...

  5. 面试回答,JVM内存模型/内存空间:运行时数据区

    发布于个人公众号,打开微信,搜索MelodyJerry即可 本文由作者原文 [JVM|内存模型] Java虚拟机的内存模型?也就这7个而已 修改而来,可点击左下角阅读原文. JVM内存模型/内存空间 ...

  6. java 线程栈空间_java线程的内存不包含在JVM堆与栈中

    Java代码   publicclassMaxThreadsTest { publicstaticvoidmain(String[] args) { while(true) { newThread(n ...

  7. java 内存情况_java查看jvm内存使用情况

    java查看jvm内存使用情况 (2012-03-22 15:50:54) 标签: jvm 内存 虚拟机 分配 it java.lang.Runtime类提供了查看当前JVM内存的使用情况.每个jav ...

  8. Java改知能机_Java 面试突击之 Java 并发知识基础 进阶考点全解析

    版权说明:本文内容根据 github 开源项目整理所得 项目地址:https://github.com/Snailclimb/JavaGuide​github.com 一.基础 什么是线程和进程? 何 ...

  9. 转:JAVA常见错误处理方法 和 JVM内存结构

    OutOfMemoryError在开发过程中是司空见惯的,遇到这个错误,新手程序员都知道从两个方面入手来解决:一是排查程序是否有BUG导致内存泄漏:二是调整JVM启动参数增大内存.OutOfMemor ...

最新文章

  1. 谈谈让你纠结的年终奖
  2. 想学Python有没有必要报班?
  3. python opencv实时显示测量数据_python OpenCV 宽度测量
  4. python bottle框架 运维_python bottle 框架实战教程:任务管理系统 V_1.0版 | linux系统运维...
  5. iOS开发 常见错误
  6. Python 标准库 —— fractions
  7. Python电影售票系统
  8. 英语视听说第六版答案
  9. cachecloud java_cachecloud安装部署
  10. 北京地铁拥挤度实时查询
  11. js中输出2000~2100年之间所有的闰年;
  12. Android 判断当前身份证格式是否正确
  13. 第一型曲线和曲面积分总结
  14. 【编程题】中国象棋路灯
  15. 微信登录界面安卓代码_「微信多开神器」一键安排你的所有微信
  16. 没有投屏标志怎么投屏_没有TV投屏标示,手机电脑电视该如何实现投屏
  17. css3-animation-复习篇
  18. 如何设置STM32 IO端口输入输出
  19. 图片瀑布流加载和购物车
  20. 【MQTT基础篇(十四)】MQTT心跳机制

热门文章

  1. 千兆上网行为管理路由评测
  2. Restoration forWeakly Blurred and Strongly Noisy Images 阅读理解
  3. UE4 EventTick
  4. 1.16 使用JTAppleCalendar制作漂亮的日历 [iOS开发中的神兵利器]
  5. 英语语法总结--虚拟语气
  6. 痛快 SpringBoot终于禁掉了循环依赖
  7. 赐你一本_.武林秘籍,Lodash常用的方法帮助你快速开发。
  8. win10可以上网但是网路连接显示小地球(无法连接到Internet)
  9. 苹果7计算机在哪里,iPhone7白苹果怎么办 iPhone7白苹果重启解决方法【详细介绍】...
  10. 《多传感器融合定位》惯性导航基础(二)