微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

JVM学习目录内存区域、直接内存

不秃顶的山羊,之后会将JVM系列总结归纳成一个思维导图,系统学习并通过发博客的方式记录自己的学习过程。
感谢您的关注,一起进步,欢迎留言交流~

目录

一):JVM 内存区域

1. 堆(Heap-线程共享)

1、堆介绍

  • (Java Heap)是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,Java世界里“几乎”所有的对象实例都在这里分配内存。由于即时编译技术的进步,尤其是逃逸分析技术的日渐强大,栈上分配、标量替换优化手段已经导致一些微妙的变化悄然发生,所以说Java对象实例都分配在堆上也渐渐变得不是那么绝对了。
  • Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的(通过参数-Xmx和-Xms设定)。如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。
  • 主要存储: New对象、数组、包括JDK1.7从方法区移过来的字符串常量和静态变量、还有 线程分配缓冲区(TLAB)

2、线程分配缓冲区(TLAB)

  • JVM是被线程共享的,而且不断频繁的产生新的对象,那么在并发情况下,为保证线程安全需要加锁,但是这样对象的分配效率又会大大折扣,因此TLAB出现,每个线程都会分配一块独立的TLAB空间(线程私有)
  • 其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,但如果对象过大的话则仍然是直接使用堆空间分配

2. 方法区(Method Area-线程共享)

1、介绍

  • 很久很久以前Hotspot方法区的实现是永久代(PermGen space),JRokit的实现是元空间(Metaspace),Oracle公司可能也意识到永久代的弊端,收购JRokit之后将Hotspot与JRokit合二为一
  • JDK1.7其实已经开始用元空间(Metaspace)替代永久代(PermGen space),只不过还不完全,直到JDK1.8才完全替代了方法区的实现

2、存储:

  • JDK1.7之前方法区存储的有:类信息(属性、方法、接口、版本等)、静态变量、字符串常量等。JDK1.7将静态变量字符串常量池移至堆内存,进而减少方法区的OutOfMemoryError
  • 可能有人会问为啥未替换之前方法区易OOM,我们可以这么理解:一个箱子存储的容量取决于两点:箱子本身大小、以及箱子将要装入东西的容量,以上的讲解可以理解为装入的东西减少,接下来咱们讲解箱子本身的容器

3、永久代(PermGen space) VS 元空间(Metaspace)

  • 永久代有(-XX:MaxPermSize)上限,即使不设置也有默认大小,易出现OOM,而默认的元空间只受本地内存大小的限制(-XX:MaxMetaspaceSize),例如32位系统钟的4GB限制

4、String.intern()

  • String.intern()方法还算比较有意思的,它先在字符串常量池查找是否存在此字符串,若存在则返回引用地址;如果不存在则在字符串常量池中创建一个等值的字符串,再返回此字符串的引用地址
  • 相关面试题可浏览:https://blog.csdn.net/weixin_39382337/article/details/109715910

3. 虚拟机栈(JVM Stack-线程私有)

1、介绍

  • 虚拟机栈是为Java方法服务的,当我们在调用Java方法的时候,每个方法在执行的同时都会创建一个栈帧用于存储基本信息【局部变量表、操作数栈、动态链接、方法出口等】。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。栈帧随着方法调用而创建,随着方法结束而销毁
  • 局部变量表:存放了编译期可知的各种Java虚拟机基本数据类型、对象引用和returnAddress类型
  • 操作树栈:变量之间的运算操作,结构是先进后出
  • 动态链接:一个方法调用另外一个方法,或者访问其成员变量

栈帧

4. 本地方法栈(Native Method Stacks-线程私有)

1、介绍

  • 本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,
    其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务

    简单的来说就是:native修饰的方法,非java语言编写的代码,如C或者C++  
    
  • 与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛StackOverflowError和OutOfMemoryError异常

5 .程序计数器(Program Counter Register-线程私有)

1、介绍

  • 程序计数器(Program Counter Registe)是一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的 程序计数器,这类内存也称为“线程私有”的内存。 正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果还是 Native 方法,则为Undefined。
  • 这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。

2、疑问

  • 可能有人会产生疑问,既然程序计数器记录native方法,这个计数器值为(Undefined),如果是多个线程切换,那如何定位其执行位置呢?
    Re: native是非java代码编写的,而是由C或C++实现的,因此JVM获取不到native实现,只能通过系统指令去调用native方法,native方法由原生平台直接执行,并不需要理会抽象的JVM层面记录的Undefined——原生的CPU上真正的PC程序计数器是怎样就是怎样。就像是一个用C或C++写的多线程程序,它在线程切换有它们自己的一套定位记录方式。

二):直接内存

1、介绍

  • 在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作
  • 简单的来说就是NIO支持Java访问直接内存,通过映射缓存的方式,提高性能,避免了重复copy的方式

2、使用场景

  • 直接内存 并不是 JVM 运行时数据区的一部分, 但也会被频繁的使用;
  • 通常,访问直接内存的速度要优于Java堆。即读写性能高。因此出于性能考虑,读写大且频繁的场合可能会考虑使用直接内存。前提是能带来很明显的性能提升

3、参数设置

  • 直接内存的大小设置(-XX:MaxDirectMemorySize)
  • 如果不指定,默认与堆的最大值-Xmx参数值一致,因此忽略直接内存的参数设置,可能使得各个内存区域总和大于物理内存限制,从而动态扩展时出现OOM。直接内存的大小也受限于操作系统给出的最大内存

不秃顶的山羊,之后会将JVM系列总结归纳成一个思维导图,系统学习并通过发博客的方式记录自己的学习过程。
不定期更新,或许您的点赞评论是小编的莫大动力,一起进步,奥利给~

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐