JVM内存结构详解
从java编程语言说起。。。
1. Java编程语言简介
1.1 编程语言概述
- 系统级和应用级
- 系统级:C,C++,go,erlang
- 应用级:C#,Java,Python,Perl,Ruby,php
- 虚拟机:jvm(java虚拟机)、pvm(python虚拟机)
- 动态网站:asp.net,jsp
- 编程语言的类别:(程序=指令+数据)
- 面向过程:以指令为中心,围绕指令组织数据
- 面向对象:以数据为中心,围绕数据组织指令
- 动态网站
- 客户端动态
- 服务器动态:CGI
- webapp server
- jsp:tomcata,jboss,jetty
- php:php-fpm
1.2 Java编程语言
1.2.1 Java的特性
- 面向对象(完全面向对象)、多线程、结构化错误处理
- 垃圾收集、动态链接、动态扩展
1.2.2 JVM、JRE、JDK
JVM(Java Virtual Machine),Java虚拟机。
它只能识别 .class类型的文件,能够将class文件中的字节码指令进行识别并调用操作系统上的API完成动作。JVM是Java能够跨平台的核心。
JRE(Java Runtime Environment),Java运行时环境。
它主要包含两个部分:jvm的标志实现、java的一些基本类库。它相对于jvm来说,多出来的是一部分的java类库。
JDK(Java Development Kit),Java开发工具包。
JDK是整个java开发的核心,它集成了JRE和一些好用的小工具,如javac、java、jar等。
三者的嵌套关系:JDK > JRE > JVM
1.2.3 Java的执行过程
- Java体系结构
- Java编程语言
- Java Class文件格式
- Java API
- Java VM
- JVM的核心组成部分
- Class Loader(类加载器)
- 执行引擎
1.2.4 Java的三个技术流派
- J2SE ==> Java 2 SE
- J2EE ==> Java 2 EE
- J2ME ==> Java 2 ME
1.3 Java 2 EE 平台
1.3.1 Servlet
Servlet是运行在web服务器上的程序,它是作为http客户端的请求和http服务器上数据库或应用程序之间的中间层,与CGI所实现的效果类似。
Servlet Container:将html标签硬编码在应用程序中,如println("<h1>")
1.3.2 JSP
JSP(Java Server Pages),是JavaWeb服务器端的动态资源,与html页面作用类似,显示数据和获取数据。
JSP=html+Java脚本(代码片段)+JSP动态标签
JSP图示:(JSP能自动将代码转换为Servlet格式)
1.3.3 Java Bean
Java Bean一般情况下指的是实体类,所有属性为private,提供默认构造方法和getter,setter,如果一个JavaBean需要在不同的JVM的进程中进行传递,还需要实现Serializable接口。
1.3.4 EJB
EJB(Enterprise JavaBean),企业级JavaBean,普通Java Bean 的区别是,JavaBean的使用可以不需要容器,EJB的运行一般需要EJB容器(即应用服务器,如JBoss/Weblogic/Websphere…
1.3.5 JMS
JMS(Java Message Service),Java消息服务接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
Java消息服务的规范包括两种消息模式,点对点和发布者/订阅者。
1.3.6 JMX
JMX(Java Management Extensions),是一个为应用程序植入管理功能的框架。JMX是一套标准的代理和服务,用户可以在任何Java应用程序中使用这些代理和服务实现管理。
1.4 Web Container
1.4.1 包含三个容器
- JDK
- Servlet
- JSP
图示:
1.4.2 实现
- 商业实现:
- WebSphere(IBM)
- WebLogic (BEA --> Oracle)
- Oc4j
- Glassfish
- Geronimo
- JOnAS
- JBoss
- 开源实现:
- Tomcat
- jetty
- resin
2. JVM运行时区域简介
2.1 JVM运行时区域图示
运行为多个线程,这些是为这些线程所使用的
OutOfMemoryError(OOM)内存溢出
2.2 Java内存结构
- Java虚拟机在执行Java程序的过程中会把它管理的内存划分为若干个不同的数据区域,每个区域都有各自的作用
- 分析JVM内存结构,主要就是分析JVM运行时数据存储区域
- JVM的运行时数据区主要包括:堆、栈、方法区、程序计数器
- JVM的优化问题主要在线程共享的数据区中:堆、方法区
图示:
2.3 各区域简介
2.3.1 方法区(共享)
- 线程共享的内存区域
- 用于存储被虚拟加载的类信息、常量、静态变量等
- 该区域也被称为:永久代(或者叫非堆内存区域)
2.3.2 堆(共享)
- Java堆是jvm所管理的内存中最大的一部分,主要包含了java运行中所存放的对象
- Java堆也是GC管理的主要区域,主流的算法都基于分代收集算法方式进行:
- 新生代
- Eden Space
- Survivor Space 存活区
- 老年代(Tenured Gen)
- 新生代
- 堆内存是共享的,所有的java线程共享的内存局域
- 堆中存放的是类实例化出来的对象
2.3.3 Java栈(线程私有)
- 每一个线程都有自己的栈,线程启动起来就自动给它创建一个栈,这个虚拟机栈中描述的是java方法执行的内存模型
- 每个方法被执行时,都会给它创建一个栈帧,用于存储线程自己的局部变量
- Java栈内存为线程私有,存放线程自己的局部变量等信息
2.3.4 PC寄存器(Program Counter Register)(线程私有)
- 程序计数器,线程独占的内存空间
- 一般是非常小的内存空间
2.3.5 本地方法栈(线程私有)
- Native Method Stacks
- 它不是通过虚拟机栈为虚拟机执行java方法,而是为虚拟机所使用到的本地方法提供服务
- 这里的本地方法对于Windows和Linux是不同的,它表明的是它真正能够在那个主机上所能执行的特有方法
- 所以具体的实施方案是依赖于平台的
2.3.6 GC 垃圾回收器(面向于堆内存空间)
- 主要目的就是回收那些不用了的对象
2.4 运行时数据区域图示
3. 栈(JVM栈 & 本地方法栈)
JVM中的栈包括Java虚拟机栈和本地方法栈
- Java虚拟机栈为JVM执行Java方法服务
- 本地方法栈则为JVM使用到的Native方法服务
3.1 本地方法栈
- JDK中有很多方法是使用Native修饰的,Native方法不是以Java语言实现的,而是以本地语言实现的(比如C或C++)
- Native方法是与操作系统直接交互的,比如通知垃圾收集器进行垃圾回收的代码System.gc(),就是使用native修饰的
3.2 JVM栈
3.2.1 栈为何物?
- 对栈的定义:
- 限定仅在表头进行插入和删除操作的线性表
- 栈的特性:
- 压栈(入栈)和弹栈(出栈)都是对栈顶元素进行操作的,所以栈是先进后出的
- 栈是线程私有的,它的生命周期与线程相同,每个线程都会分配一个栈的空间,即每个线程又有独立的栈空间
图示:
3.2.2 栈中存储的是神马?
- 栈帧是栈的元素,每个方法在执行时都会创建一个栈帧
- 栈帧中存储了局部变量表、操作数栈、动态链接和方法出口等信息
- 每个方法从调用到运行结束的过程,就对应着一个栈帧在栈中压栈到出栈的过程
图示:
3.2.3 JVM栈中存储的内容详解
3.2.3.1 局部变量表:
- 栈帧中,由一个局部变量表存储数据
- 局部变量表中存储了基本数据结构(boolean、byte、char、short、int、float、long、double)的局部变量(包括参数)
- 和对象的引用(String、数组、对象等),但是不存储对象的内容
- 局部变量表所需的内存空间在编译期间完成分配,在方法运行期间不会改变局部变量表的大小
- 局部变量的容量以变量槽(Variable Slot)为最小单位,每个变量槽最大存储32位的数据类型
- 对于64位的数据类型(long、double),JVM会为其分配两个连续的变量槽来存储
- JVM通过索引定位的方式使用局部变量表,索引的范围从0开始至局部量表表中最大的Slot数量
- 普通方法与static方法在第0个槽位的存储有所不同,非static方法的第0个槽位存储方法所属对象实例的引用
图示:
3.2.3.2 局部变量表的Slot复用:
1. 为了尽可能的节省栈帧空间,局部变量表中的Slot是可以复用的
- 方法中定义的局部变量,其作用域不一定会覆盖整个方法
- 当方法运行时,如果已经超出了某个变量的作用域,即变量失效了,那这个变量对应的Slot就可以交给其他变量使用
2. Slot复用示例:
public void test(boolean flag)
{
if(flag)
{
int a = 66;
}
int b = 55;
}
- 过程分析:
- 当虚拟机运行test方法时,就会创建一个栈帧,并压入到当前线程的栈中
- 当运行到int a=66时,在当前栈帧的局部变量中创建一个Slot存储变量a,
- 当运行到int b=55时,此时已经超出变量a的作用域了(变量a的作用域在{}所包含的代码块中)
- 此时a就失效了,变量a占用的Slot就可以交给b来使用,这就是Slot复用
- Slot复用虽然节省了栈帧空间,但是会伴随一些额外的副作用,Slot的复用会直接影响到系统的垃圾收集行为
3. Slot复用的原理分析:
写法一:
public class TestDemo {
public static void main(String[] args){
byte[] placeholder = new byte[64 * 1024 * 1024];
System.gc();
}
}
- 分析:
- 上述代码先向内存中填充了64M的数据,然后通知虚拟机进行垃圾回收
- 虚拟机没有回收这64M内存,
- 因为执行System.gc()方法时,变量placeholder还在作用域范围内,
- 虚拟机是不会回收的,它还是有效的
写法二:
public class TestDemo {
public static void main(String[] args){
{
byte[] placeholder = new byte[64 * 1024 * 1024];
}
System.gc();
}
}
- 分析:
- 当运行到System.gc()方法时,变量placeholder的作用域已经失效了
- 但是虚拟机还是没有回收placeholder变量占用的64M内存
- 因为虽然限定了placeholder的作用域,但是之后并没有任何对局部变量表的读写操作
- placeholder变量在布局变量表中占用的Slot没有被其他变量所复用
- 所以作为GC Roots一部分的局部变量表仍然保持着对它的关联,所以placeholder变量没有被回收
- 当运行到System.gc()方法时,变量placeholder的作用域已经失效了
写法三:
public class TestDemo {
public static void main(String[] args){
{
byte[] placeholder = new byte[64 * 1024 * 1024];
}
int a = 0;
System.gc();
}
}
- 分析:
- 此时placeholder变量占用的64M内存空间被回收了
- 运行到int a=0时,已经超过了placeholder变量的作用域,此时placeholder在局部变量表中占用的Slot可以交给其他变量使用
- 而变量a正好复用了placeholder占用的Slot,至此局部变量表中的Slot已经没有placeholder的引用了
- 虚拟机就回收了placeholder占用的64M内存空间
placeholder变量能否被回收的关键:
- 局部变量表中的Slot是否还存有关于placeholder对象的引用
3.2.3.3 操作数栈:
- 操作数栈是一个先进先出栈,操作数栈的元素可以是任意的Java数据类型
- 方法刚开始执行时,操作数栈是空的,在方法执行过程中,通过字节码指令对操作数栈进行压栈和出栈的操作
- 通常进行算术运算的时候是通过操作数栈来进行的,又或者是在调用其他方法的时候通过操作数栈进行参数传递
- 操作数栈可以理解为栈帧中用于计算的临时数据存储区
3.2.3.4 栈中可能出现的异常
- StackOverflowError:栈溢出错误
- 如果一个线程在计算时所需要用到栈大小 > 配置允许最大的栈大小,
- 那么Java虚拟机将抛出StackOverflowError
- OutOfMemoryError:内存不足
- 栈进行动态扩展时如果无法申请到足够内存,会抛出OutOfMemoryError异常
3.2.3.5 设置栈参数:
- 使用-Xss设置栈大小,通常几百k就够用了
- 由于栈是线程私有的,线程数越多,占用栈空间越大
- 栈决定了函数调用的深度,这也是慎用递归调用的原因
- 递归调用时,每次调用方法都会创建栈帧并压栈
- 当调用一定次数之后,所需栈的大小已经超过了虚拟机运行配置的最大栈参数,就会抛出StackOverflowError异常
4. Java堆(Java Heap)
4.1 Java堆概述
4.1.1 java堆简介
- 堆是java虚拟机所管理的内存中最大的一块存储区域,堆内存被所有线程共享
- 堆中主要存放的是使用new关键字创建的对象,所有对象实例以及数组都要在堆上分配
- 垃圾收集器就是根据GC算法,收集堆上对象所占用的内存空间(收集的是对象占用的空间而不是对象本身)
4.1.2 java堆的空间
- 年轻代(Young Generation)
- 伊甸园(Eden区)
- 幸存区(Survivor区)
- From Survivor空间(S0,存活区1)
- To Survivor空间(S1,存活区2)
- 老年代(Old Generation)
- 永久代(Permanent Generation)
4.2 Java堆的参数设置
4.2.1 Java堆内存图示
4.2.2 Java堆内存空间的调整参数
4.3 垃圾回收(GC)
4.3.1 年轻代和老年代的内存清理机制
- 年轻代:mark(标记)---> compact(打包)---> 清除
- 年轻代存储“新生对象”,新创建的对象存储在年轻代中
- 当年轻代内存占满之后,会触发Minor GC,清理年轻代内存空间
- 老年代
- 老年代存储长期存活的对象和大对象
- 年轻代中存储的对象,结果多次GC后仍然存活的对象会移动到老年代中存储
- 老年代空间占满后,会触发Major GC(Full GC)
- Full GC
- Full GC是清理整个堆空间,包括年轻代和老年代
- 如果Full GC之后,堆中仍然无法存储对象,就会抛出OutOfMemoryError异常
4.3.2 Java中垃圾回收的过程
4.3.2.1 Eden区
- 当一个实例被创建了,首先会被存储在堆内存年轻代的Eden区中
4.3.2.2 Survivor区(S0和S1)
- 作为年轻代GC(Minor GC)周期的一部分,存活的对象从Eden区被移动到Survivor区的S0中
- 类似的,垃圾回收器会扫描S0,然后将存活的实例移动到S1中
- 死亡的实例被标记为垃圾回收
- 根据垃圾回收器选择的不同,要么被标记的实例都会不停的从内存中移除,要么回收过程会在一个单独的进程中完成
4.3.2.3 老年代(Old or tenured generation)
- 老年代是内存中的第二块逻辑区
- 当垃圾回收器执行Minor GC周期时,在S1 Survivor区中的存活实例将会被晋升到老年代,而未被引用的对象被标记为回收
- 老年代GC(Major GC)
- Major GC扫描老年代的垃圾回收过程
- 相对于Java垃圾回收过程,老年代是实例生命周期的最后阶段
- 如果实例不再被引用,那么它们会被标记为回收,否则它们会继续留在老年代中
4.3.2.4 内存碎片
- 一旦实例从堆内存中被删除,其位置就会变空并且可用于未来实例的分配
- 这些空出的空间将会使整个内存区域碎片化,为了实例的快速分配,需要进行碎片整理
- 基于垃圾回收器的不同选择,回收的内存区域要么被不停地被整理,要么在一个单独的GC进程中完成
4.3.3 Minor GC和Full GC的触发条件
- Minor GC触发条件
- 当Eden区满时,触发Minor GC
- Full GC触发条件
- 调用System.gc时,系统建议执行Full GC,但是不一定执行
- 老年代空间不足
- 方法区空间不足
- 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
- 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
4.3.4 JVM下发生的停顿现象
4.3.4.1 Stop The World
- Java中Stop-The-World机制简称STW
- 是在执行垃圾收集算法时,Java应用程序的其他线程都被挂起(除了垃圾收集帮助器之外)
- Java中一种全局暂停现象,全局停顿,
- 所有Java代码停止,native代码可以执行,但不能与JVM交互,这些现象多半是由于gc引起
4.3.4.2 VM Thread(JVM里的特殊线程)
- 专门用来执行一些特殊的VM Operation,比如分派GC,thread dump等
- 这些任务,都需要整个Heap,以及所有线程的状态是静止的,一致的才能进行
- 所以JVM引入了安全点(Safe Point)的概念,想办法在需要进行VM Operation时,通知所有的线程进入一个静止的安全点
- 除了GC,其他触发安全点的VM Operation包括:
- JIT相关,比如Code deoptimization, Flushing code cache
- Class redefinition (e.g. javaagent,AOP代码植入的产生的instrumentation)
- Biased lock revocation 取消偏向锁
- Various debug operation (e.g. thread dump or deadlock check)
4.3.5 于Tomcat而言
- catalina.sh中有两个环境变量:
- CATALINA_OPTS:仅对启动运行tomcat实例的java虚拟机有效
- JAVA_OPTS:对本机上的所有java虚拟机有效
5. PC寄存器 & 方法区
5.1 程序计数器(PC寄存器)
- 程序计数器(Porgram Counter Register)是一块较小的内存空间
- 可以看做是当前线程所执行字节码的行号指示器,指向下一个将要执行的指令代码,由执行引擎来读取下一条指令
- 线程的执行与程序计数器:
- 一个线程的执行,是通过字节码解释器改变当前线程的计数器的值,来获取下一跳需要执行的字节码指令,从而确保线程的正确执行
- 为了确保线程切换后(上下文切换)能恢复到正确的执行位置,每个线程都有一个独立的程序计数器,各个线程的计数器互不影响,独立存储
- 程序计数器记录的内容:
- 如果线程执行Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址
- 如果执行的是Native方法,计数器值为Undefined
- 注意:
- 程序计数器是线程私有的内存
- 程序计数器不会发生内存溢出(OutOfMemoryError即OOM)问题
5.2 方法区
5.2.1 方法区(Method Area)
- 方法区同Java堆一样是被线程共享的区域
- 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码
- 静态变量+常量+类信息(版本、方法、字段等)+运行时常量池存在方法区中
- 该区域也被称为:永久代(或者叫非堆内存区域)
- 常量池是方法区的一部分
- 注意:
- JDK1.8 使用元空间 MetaSpace 替代方法区,元空间并不在 JVM中,而是使用本地内存
- 元空间两个参数:
- MetaSpaceSize:初始化元空间大小,控制发生GC阈值
- MaxMetaspaceSize : 限制元空间大小上限,防止异常占用过多物理内存
5.2.2 常量池
- 常量池中存储编译器生成的各种字面量和符号引用
- 字面量:字面量就是Java中常量的意思,比如文本字符串,final修饰的常量等
- 符号引用:符号引用包括类和接口的全限定名,方法名和描述符,字段名和描述符等
- 常量池的作用
- 优点:常量池避免了频繁的创建和销毁对象而影响系统性能,实现了对象的共享
- 基本数据类型比较的是数值,而引用数据类型比较的是内存地址
JVM内存结构详解的更多相关文章
- JVM之内存结构详解
对于开发人员来说,如果不了解Java的JVM,那真的是很难写得一手好代码,很难查得一手好bug.同时,JVM也是面试环节的中重灾区.今天开始,<JVM详解>系列开启,带大家深入了解JVM相 ...
- Java内存结构详解
Java内存结构详解 Java把内存分成:栈内存,堆内存,方法区,本地方法区和寄存器等. 下面分别介绍栈内存,堆内存,方法区各自一些特性: 1.栈内存 (1)一些基本类型的变量和对象的引用变量都是在函 ...
- JVM 内存溢出详解(栈溢出,堆溢出,持久代溢出、无法创建本地线程)
出处: http://www.jianshu.com/p/cd705f88cf2a 1.内存溢出和内存泄漏的区别 内存溢出 (Out Of Memory):是指程序在申请内存时,没有足够的内存空间供 ...
- 02-java性能调优-JVM内存模型详解
JVM整体结构与内存模型之间的关系 JVM整体结构图如下: 先贴一个代码: package com.jvm.jvmCourse2; public class Math { public static ...
- [java] 虚拟机(JVM)底层结构详解[转]
本文来自:曹胜欢博客专栏.转载请注明出处:http://blog.csdn.net/csh624366188 在以前的博客里面,我们介绍了在java领域中大部分的知识点,从最基础的java最基本语法到 ...
- jvm内存GC详解
一.相关概念 a. 基本回收算法 1. 引用计数(Reference Counting) 比较古老的回收算法.原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数.垃圾回收时,只用收 ...
- JVM内存区域详解
本文分为两部分:一是JVM内存区域的讲解:二是常见的内存溢出异常分析. 1.JVM内存区域 java虚拟机在执行java程序的过程中会把它管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途, ...
- JVM内存参数详解以及配置调优
基本概念:PermGen space:全称是Permanent Generation space.就是说是永久保存的区域,用于存放Class和Meta信息,Class在被Load的时候被放入该区域He ...
- java之 JVM 内存管理详解
一.JVM结构 根据<java虚拟机规范>规定,JVM的基本结构一般如下图所示: 从左图可知,JVM主要包括四个部分: 1.类加载器(ClassLoader):在JVM启动时或者在类运行时 ...
随机推荐
- EMAIL、用户名测试点
EMAIL xxxaa@xxx.xxx 1.没有@情况,如:aa.net 2.没有.符号,如:aa@qqcom 3..后面没有字符:如 xxxaa@xxx. 4..不在@后面, 如:xxxaa.@xx ...
- kubernetes pod的弹性伸缩———基于pod自定义custom metrics(容器的IO带宽)的HPA
背景 自Kubernetes 1.11版本起,K8s资源采集指标由Resource Metrics API(Metrics Server 实现)和Custom metrics api(Promet ...
- Hibernate实现limit查询报错 :unexpected token: limit near line 1, column 33 [from pojo.Product p order by id limit ?,? ]
原因: hibernate无法识别limit, hql语句更不能这样写String hql="from Product p order by id limit ?,? "; 解决 ...
- 【pyecharts】地图显示不全
官网给的解释如下: 自从 0.3.2 开始,为了缩减项目本身的体积以及维持 pyecharts 项目的轻量化运行,pyecharts 将不再自带地图 js 文件. 如用户需要用到地图图表,可自行安装对 ...
- Python可变对象和不可变对象
Python中一切皆对象,每个对象都有其唯一的id,对应的类型和值,其中id指的是对象在内存中的位置.根据对象的值是否可修改分为可变对象和不可变对象.其中, 不可对象包括:数字,字符串,tuple 可 ...
- Python - 常用的PyCharm的快捷键和使用场景介绍
关于PyCharm的快捷键,由于数量众多,差不多有100个,相信几乎没有人会记住所有,每个人都会有自己顺手的几个,这里我将自己用着顺手,不别扭的快捷键分享出来,同时分享在哪里可以找到所有的快捷键. 一 ...
- Docker scratch 无法正常运行golang二进制程序的问题
使用Docker构建容器能够极大的降低运维成本,提高部署效率,同时非常方便对服务的平行扩展.然而在构建容器镜像过程中的,存在着一个难以避免的问题,就是如果使用常见的发行版本作为程序运行的基础环境,那么 ...
- python的map,reduce函数与pandas的apply,filter函数
1. python自带的apply.filter.map函数.reduce函数,很多情况下可以代替for循环: map(func,list),对list的每个元素分别执行func函数操作,显然func ...
- cors跨越深度刨析
解决跨域的方式JSOP,和CORS JSONP不做介绍了. CORS跨域: 参考阮一峰http://www.ruanyifeng.com/blog/2016/04/cors.html 官方:https ...
- SSL/TLS/WTLS
转载来自http://blog.csdn.net/fw0124/article/details/8470940 一 前言 首先要澄清一下名字的混淆: 1 SSL(Secure Socket Layer ...