JVM内存模型-程序员宅基地

技术标签: java  运维  c/c++  

1. JVM内存模型

JVM内存模型

2. 程序计数器(PC)

每个线程都会有自己私有的程序计数器(PC)。可以看作是当前线程所执行的字节码的行号指示器。 也可以理解为下一条将要执行的指令的地址或者行号。字节码解释器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、 循环、 跳转、 异常处理、 线程上下文切换,线程恢复时,都要依赖PC. - 如果线程正在执行的是一个Java方法,PC值为正在执行的虚拟机字节码指令的地址 - 如果线程正在执行的是Native方法,PC值为空(未定义)

3. 虚拟机栈(VM Stack)

VM Stack也是线程私有的区域。他是java方法执行时的字典:它里面记录了局部变量表、 操作数栈、 动态链接、 方法出口等信息。 在《java虚拟机规范》一书中对这部分的描述如下:

栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接 (Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。
栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。
栈帧的存储空间分配在 Java 虚拟机栈之中,每一个栈帧都有自己的局部变量表( Local Variables)、操作数栈( OperandStack)和指向当前方法所属的类的运行时常量池的引用。

说白了,VM Stack是一个栈,也是一块内存区域。 所以,他是有大小的。虽然有大小,但是一般而言,各种虚拟机的实现都支持动态扩展这部分内存。 - 如果线程请求的栈深度太大,则抛出StackOverflowError - 如果动态扩展时没有足够的大小,则抛出OutOfMemoryError 栈的大小

栈的大小可以受到几个因素影响,一个是jvm参数 -XSS,默认值随着虚拟机版本以及操作系统影响,从Oracle官网上我们可以找到:

In Java SE 6, the default on Sparc is 512k in the 32-bit VM, and 1024k in the 64-bit VM. On x86 Solaris/Linux it is 320k in the 32-bit VM and 1024k in the 64-bit VM.

我们可以认为64位linux默认是1m的样子。 除了JVM设置,我们还可以在创建Thread的时候手工指定大小:

public Thread(ThreadGroup group, Runnable target, String name , long stackSize)

栈的大小影响到了线程的最大数量,尤其在大流量的server中,我们很多时候的并发数受到的是线程数的限制,这时候需要了解限制在哪里。 第一个限制在操作系统,以ubuntu为例,/proc/sys/kernel/threads-max 和/proc/sys/vm/max_map_count 定义了总的最大线程数(根据资料windows总的来说线程数会更少)和mmap这个system_call的最大数量(也就是从内存方面限制了线程数) 第二个限制自然是在JVM,理论上我们能分配给线程的内存除以单个线程占用的内存就是最大线程数。所以说对Java进程来讲,既然分配给了堆,栈和静态方法区(或叫永久代,perm区),我们可以大致认为

线程数 = (系统空闲内存-堆内存(-Xms, -Xmx)- perm区内存(-XX:MaxPermSize)) / 线程栈大小(-Xss)

注意这只是帮助我们树立一个概念,实际上还有许多因素影响。

栈的大小还影响到一个就是如果单个栈超过了这个大小,就会抛出StackOverflowError,一般来说递归调用是常见的原因。

如何查看线程栈

使用命令 jstack <pid>可以列出当前pid对应jvm的所有线程栈描述,描述主要包括了每个线程的状态以及堆栈内各栈帧的方法全限定名,代码位置。注意这只是为了可阅读性,并不是说栈里存着的就是这些字符串。

4. 本地方法栈(Native Method Stack)

Java 虚拟机实现可能会使用到传统的栈(通常称之为“ C Stacks”)来支持 native 方法( 指使用 Java 以外的其他语言编写的方法)的执行,这个栈就是本地方法栈( Native MethodStack)。

VM Stack是为执行java方法服务的,此处的Native Method Stack是为执行本地方法服务的。 此处的本地方法指定是和具体的底层操作系统层面相关的接口调用了(这部分太高高级了,不想深究……)。

《java虚拟机规范》中没有对这部分做具体的规定。所以就由VM的实现者自由发挥了。 有的虚拟机(比如HotSpot)将VM Stack和Native Method Stack合二为一,所以VM的另一种内存区域图就如下面所示了: JVM内存模型

5. Java堆(Heap)

在 Java 虚拟机中,堆( Heap)是可供各条线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域。

以下是本人对《java虚拟机规范》一书中对Java堆的介绍的总结:

  • 在虚拟机启动的时候就被创建
  • 是所有线程共享的内存区域
  • 存储了被自动内存管理系统所管理的各种对象
    • 这些受管理的对象无需,也无法显式地被销毁
    • 自动内存管理系统:Automatic StorageManagement System,也即是常说的”Garbage Collector(垃圾收集器)”
    • 并未指明用什么具体的技术去实现自动内存管理系统
  • Java 堆的容量可以是固定大小的,也可以随着程序执行的需求动态扩展,并在不需要过多空间时自动收缩
  • Java 堆所使用的内存不需要保证是连续的
  • 如果实际所需的堆超过了自动内存管理系统能提供的最大容量,那 Java 虚拟机将会抛出一个OutOfMemoryError 异常
  • 实现者应当提供给程序员或者最终用户调节 Java 堆初始容量的手段
    • 对于可以动态扩展和收缩 Java 堆来说,则应当提供调节其最大、最小容量的手段
  • 所有的对象实例以及数组都要在堆上分配

首先堆可以划分为新生代和老年代。

新生代 然后新生代又可以划分为一个Eden区和两个Survivor(幸存)区。 按照规定,新对象会首先分配在Eden中(如果对象过大,比如大数组,将会直接放到老年代)。在GC中,Eden中的对象会被移动到survivor中,直至对象满足一定的年纪(定义为熬过minor GC的次数),会被移动到老年代。

新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ) 默认的,Eden : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。

然后讲讲垃圾收集

堆内存和垃圾收集是密不可分的两个主题,讲垃圾收集的资料很多,但总的来说讲的比较混乱,在这里我试图从一个系统的视角展示垃圾收集。

  • 垃圾收集的意义

    • 垃圾收集的出现解放了C++中手工对内存进行管理的大量繁杂工作,手工malloc,free不仅增加程序复杂度,还增加了bug数量。
    • 分代收集。即在新生代和老生代使用不同的收集方式。在垃圾收集上,目标主要有:加大系统吞吐量(减少总垃圾收集的资源消耗);减少最大STW(Stop-The-World)时间;减少总STW时间。不同的系统需要不同的达成目标。而分代这一里程碑式的进步首先极大减少了STW,然后可以自由组合来达到预定目标。
  • 可达性检测

    • 引用计数:一种在jdk1.2之前被使用的垃圾收集算法,我们需要了解其思想。其主要思想就是维护一个counter,当counter为0的时候认为对象没有引用,可以被回收。缺点是无法处理循环引用。目前iOS开发中的一个常见技术ARC(Automatic Reference Counting)也是采用类似的思路。在当前的JVM中应该是没有被使用的。
    • 根搜算法:思想是从gc root根据引用关系来遍历整个堆并作标记,称之为mark,等会在具体收集器中介绍并行标记和单线程标记。之后回收掉未被mark的对象,好处是解决了循环依赖这种『孤岛效应』。这里的gc root主要指:
      • 虚拟机栈(栈桢中的本地变量表)中的引用的对象
      • 方法区中的类静态属性引用的对象
      • 方法区中的常量引用的对象
      • 本地方法栈中JNI的引用的对象
  • 整理策略

    • 复制:主要用在新生代的回收上,通过from区和to区的来回拷贝。需要特定的结构(也就是Young区现在的结构)来支持,对于新生成的对象来说,频繁的去复制可以最快的找到那些不用的对象并回收掉空间。所以说在JVM里YGC一定承担了最大量的垃圾清除任务。
    • 标记清除/标记整理:主要用在老生代回收上,通过根搜的标记然后清除或者整理掉不需要的对象。

思考一下复制和标记清除/整理的区别,为什么新生代要用复制?因为对新生代来讲,一次垃圾收集要回收掉绝大部分对象,我们通过冗余空间的办法来加速整理过程(不冗余空间的整理操作要做swap,而冗余只需要做move)。同时可以记录下每个对象的『年龄』从而优化『晋升』操作使得中年对象不被错误放到老年代。而反过来老年代偏稳定,我们哪怕是用清除,也不会产生太多的碎片,并且整理的代价也并不会太大。

  • 具体的垃圾收集器(重点了解CMSGC和G1)
    • 新生代收集器:有Serial收集器、ParNew收集器、Parallel Scavenge收集器
    • 老生代收集器:Serial Old收集器、Parallel Old收集器、CMS收集器、G1收集器

静态方法区

最后讲一讲静态方法区,又称为永久代(Perm Generation)。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 常见的JVM配置包括:

-XX:MaxPermSize=512m

我们有时候会看到java进程报一个错误类似

Exception in thread "State Saver" java.lang.OutOfMemoryError: PermGen space

说明我们此时要调整配置了,或者说代码中有一些bug导致大量的perm区被占用,可能是用到了太多的静态变量(一般怀疑map)或者说用到ASM框架导致产生了大量的类信息。

JVM内存模型

6. 方法区(Method Area)

方法区是由所有线程共享的内存区域。 方法区存储的大致内容如下:

  • 每一个类的结构信息
    • 运行时常量池( Runtime Constant Pool)
    • 字段和方法数据
    • 构造函数和普通方法的字节码内容
  • 类、实例、接口初始化时用到的特殊方法 每一个运行时常量池都分配在 Java 虚拟机的方法区之中,在类和接口被加载到虚拟机后,对应的运行时常量池就被创建出来。
  • 当创建类或接口的时候,如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值,那 Java 虚拟机将会抛出一个 OutOfMemoryError 异常。

7. 直接内存(Direct Memory)

此处的直接内存并不是由JVM管理的内存。他是利用本地方法库直接在java堆之外申请的内存区域。 比如NIO中的DirectByteBuffer就是操作直接内存的。

直接内存的好处就是避免了在java堆和native堆直接同步数据的步骤。但是他并不是由JVM来管理的。

参考一

参考二

转载于:https://my.oschina.net/u/866172/blog/1818455

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/chijiang6541/article/details/100796197

智能推荐

IDEA-项目文件旁边出现0%classes,0% lines covered_idea模块中的0%是什么-程序员宅基地

文章浏览阅读1.7k次,点赞6次,收藏2次。本来想执行Debug的,不小心点成了下面的项目中的文件就带着0%classes,0% lines covered使用ctrl + ALT + F6弹出如下框,去掉勾选后点击Show Selected就可以去掉了..._idea模块中的0%是什么

从SpringFox向SpringDoc转移(OpenAPI2向OpenAPI3转移)_springfox 3.0 转换为 springdoc-openapi-程序员宅基地

文章浏览阅读3.9k次,点赞3次,收藏11次。转移原因在学习使用spring集成swagger3时查阅文档发现SpringFox未支持 OpenAPI3 标准,而是还在支持2017年就已经停止维护的OpenAPI2了而搜遍全网写OpenAPI3的教程少的可怜但还是找到了与之相关的文章文章跳转但没有关于权限验证的相关教程,答案还得去官网找官网链接转移步骤删除springfox和swagger 2依赖项。而是添加springdoc-openapi-ui依赖2.替换注解3.替换Docket添加OpenAPI类型的_springfox 3.0 转换为 springdoc-openapi

部署在阿里云 SLB 后面的spring 应用如何获得用户真实 IP_阿里 slb穿透真实ip-程序员宅基地

文章浏览阅读1.6k次。如果你的应用是部署在阿里云上面的, 往往在应用的前面会架设一个SLB(负载均衡). 如果 SLB配置成四层转发, 那么你的应用看到的 http 连接的对端地址为真实的用户 IP, `HttpServletRequest.getRemoteAddr()`能返回正确的用户 IP. 但是如果 SLB 配置成`七层转发`, `HttpServletRequest.getRemoteAddr()`返回的是..._阿里 slb穿透真实ip

基于TensorRT7.1、Libtorch、Caffe、ROS下的安装和测试_ros tensorrt-程序员宅基地

文章浏览阅读1.5k次。1: TensorRT7.1.3.4安装下载链接:https://developer.nvidia.com/tensorrttar -xvzf TensorRT-7.1.3.4.Ubuntu-16.04.x86_64-gnu.cuda-10.2.cudnn8.0.tar.gzcd datapython3 ./download_pgms.pycd samplepip install pillow../bin/sample_mnistexport LD_LIBRARY_PATH=/home/_ros tensorrt

nginx配置部署一个域名,多个端口_nginx一个域名转发多个端口-程序员宅基地

文章浏览阅读3.8k次,点赞5次,收藏4次。nginx部署+反向代理+部署同一域名不同端口号_nginx一个域名转发多个端口

电子学会2022年9月青少年软件编程(图形化)等级考试试卷(二级)答案解析_2022.09全国青少年软件编程(图形化)等级考试试卷(二级)-程序员宅基地

文章浏览阅读988次。D队负于A队和B队,胜C队,得3分。试题解析:程序执行流程为:最初角色的大小为10.然后增加20,角色的大小为30,然后角色的大小增加-20,角色大小变成10,然后将角色大小设为30,将角色大小增加10,角色的大小为40. 故答案选D。试题解析:角色初始位置为(0,0),程序执行后x增加100,y增加100,后位置为(100,100),最后是将y坐标设为100,故程序执行完,角色坐标为(100,100)。丙认为A、B、D、E或F是冠军,因为只有一个人正确,所以冠军为C、D或F,但答案只有C,故答案为C。_2022.09全国青少年软件编程(图形化)等级考试试卷(二级)

随便推点

2024年Android面试笔试总结(Android精心整理篇)-程序员宅基地

文章浏览阅读236次,点赞4次,收藏9次。在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。一个人可以走的很快,但一群人才能走的更远。

c++11的异步线程操作_c++异步线程-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏12次。一、异步线程无论是在哪种语言中,都会面临异步操作的问题。基本上异步操作的实现可以大致分为系统级和应用级(封装的库也算应用级)。系统级一般是通过中断或者线程实现,在应用层面上一般是通过线程来实现。异步操作的目的是为了提高响应的并发量和控制访问的安全性以及健壮性。说的再直白一些,就是把访问过程,处理过程和响应过程分离。异步是相对于同步来说,同步相当于一问一答,必须实现,假如你去银行办理业务,你问柜台的小姐姐一句话,半天才回复你,估计你就怒了。但是如果你去带着一些木料去定作家具,就不愿意等在那儿,而愿..._c++异步线程

MySQL安装教程(详细版)_mysql8.0.36安装教程-程序员宅基地

文章浏览阅读1.6w次,点赞77次,收藏228次。MySQL免费安装教程、如何验证MySQL是否安装成功?怎么关掉MySQL服务?MySQL 端口问题如何解决?MySQL如何开机自启OR手动自启?安装MySQL时Starting the server 安装失败怎么解决?_mysql8.0.36安装教程

数据库添加约束_数据库怎样增加约束关系-程序员宅基地

文章浏览阅读631次。--添加主键约束(stuNo作为主键,stuInfo为表名)alter table stuInfoadd constraint PK_stuNo PRIMARY KEY (stuNo)--添加唯一约束alter table stuInfoadd constra_数据库怎样增加约束关系

python安装模块时显示*.whl is not a supported wheel on this platform.解决方法_error: wxpython-4.2.1-cp312-cp312-win_amd64.whl is-程序员宅基地

文章浏览阅读1.7w次,点赞3次,收藏3次。找了一下午相关方法,包括强行改格式也都试了一遍,发现还是不行……于是强行升级python版本(从2.7升级到3.6)……然后,成功安装whl文件!(请忽略这个渣渣网速……)我觉得是升级python顺带升级了pip的关系!如果还是不行的话,就只能试试强行改格式名了……在shell中输入import pip; print(pip.pep425tags.get_supported_error: wxpython-4.2.1-cp312-cp312-win_amd64.whl is not a supported wheel on

Solox 性能数据实时收集工具-程序员宅基地

文章浏览阅读1.6k次。关于App性能测试工具非常见,比如腾讯的GT早已不在维护,印象比较深的是几年前用过的一款收费工具叫gamebench,支持Android/iOS平台。目前比较主流的是 PerfDog?同样是腾讯出的收费工具。有使用过的同学可以评价一下。本文要介绍的工具是SoloX, 是一款开源 Android/iOS 性能数据实时收集工具。_solox