Jvm原理剖析与调优之内存结构

简介:
原创作品,允许转载,转载时请务必以超链接形式标明文章  原始出处 、作者信息和本声明。否则将追究法律责任。 http://dba10g.blog.51cto.com/764602/1637276

一些不得不说的概念

JVM

JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。 JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。是运行java应用最底层部分。

JDK(Java Development kit)

整个Java的核心,包括了Java运行环境(Java Runtime Envirnment),一堆Java工具(编译,debug等)和Java基础的类库(rt.jar)。是开发java应用的基础。

JRE(Java Runtime Environment,Java运行环境)

运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。运行java应用的基础。

J2SE(Java 2 Platform,Standard Edition)。包含那些构成Java语言核心的类。比如:数据库连接、接口定义、输入/输出、网络编程

J2EE(Java 2 Platform,Enterprise Edition)。Enterprise Edition(企业版) J2EE 包含J2SE 中的类,并且还包含用于开发企业级应用的类。比如:EJB、servlet、JSP、XML、事务控制

J2ME(Java 2 Platform,Micro Editon)



主要JVM

首先,JVM是一套规范。很多公司均实现了各自的虚拟机。常见的有

HotSpot JVM(sun)
Jrockit JVM(BEA公司的JVM,应用于weblogic)
IBM JVM

Apache Harmony

其中,我们常用的是HotSpot JVM.


JVM结构

spacer.gifwKioL1U3kyvio48NAAKNdk2Zrrk033.jpg


第 一步(编译): 

创建完源文件之后,程序会先被编译为.class文件。Java编译一个类时,如果这个类所依赖的类还没有被编译,编译器就会先编译这个被依赖的类,然后 引用,这个有点象make。如果java编译器在指定目录下找不到该类所其依赖的类的.class文件或者.java源文件的话,编译器话 报“cant find symbol”的错误。

第二步(运行):

java类运行的过程大概可分为两个过程:1、类的加载  2、类的执行。

需要说明的是:JVM主要在程序第一次主动使用类的时候,才会去加载该类。也就是说,JVM并不是在一开始就把一个程序就所有的类都加载到内存中,而是到不得不用的时候才把它加载进来,而且只加载一次。

  1. 在 编译好java程序得到MainApp.class文件后,在命令行上敲java AppMain。系统就会启动一个jvm进程,jvm进程从classpath路径中找到一个名为AppMain.class的二进制文件,将 MainApp的类信息加载到运行时数据区的方法区内,这个过程叫做MainApp类的加载。

  2. (java命令)然后JVM找到AppMain的主函数入口,开始执行main函数

  3. (类加载器)执行过程中,会创建对象。JVM会首先从方法区加载类信息和相关常量,class加载完毕之后,在堆上为对象分配内存,然后调用初始化实例,当然这时候实例保持指向class类型信息,这个信息保存在方法区中。

  4. (执行引擎)调用实例方法时,会根据引用找到对象信息,进而可定位对应的class类型信息,和方法表。

  5. (执行引擎)执行方法时,在虚拟机栈中进行,分配栈帧,随着入栈出栈,完成方法调用操作。

执行引擎

运行Java的每一个线程都是一个独立的虚拟机执行引擎的实例。从线程生命周期的开始到结束,他要么在执行字节码,要么在执行本地方法。一个线程可能通过解释或者使用芯片级指令直接执行字节码,或者间接通过JIT执行编译过的本地代码。我们上文讲到的main函数,也就是执行引擎的操作入口。

Class文件 

实际上,Class文件中方法的字节码流就是有JVM的指令序列构成的。每一条指令包含一个单字节的操作码,后面跟随0个或多个操作数。

iload_0    // 把存储在局部变量区中索引为0的整数压入操作数栈。

iload_1    // 把存储在局部变量区中索引为1的整数压入操作数栈。

iadd         // 从操作数栈中弹出两个整数相加,在将结果压入操作数栈。

istore_2   // 从操作数栈中弹出结果

JVM运行时数据区

1)程序计数器(线程私有)

当前线程所执行的字节码的行号指示器,通过改变这个计数器的值,确定下一条要执行的命令。分支,循环,跳转都需要它的支持。

它是线程私有的,每个线程都有专属于自己的程序记数器,线程之间互不影响,独立存储,保证了线程切换后,可以恢复到原先执行位置。

2)Java虚拟机栈(线程私有)

每个方法的执行,同时都会在虚拟机栈上创建一个栈帧。用于存储局部变量表,操作数栈,方法出口,动态链接等。一个方法的执行周期,同时也就对应着栈帧的出栈入栈操作。有时候方法的递归,会造成大量的栈帧,达到一定的深度,会报StackOverflowError异常。有一点需要说明:在编译器编译Java代码时,就已经在字节码中为每个方法都设置好了局部变量区和操作数栈的数据和大小。并在JVM首次加载方法所属的Class文件时, 就将这些数据放进了方法区。因此在线程调用方法时,只需要根据方法区中的局部变量区和操作数栈的大小来分配一个新的栈帧的内存大小,并堆入Java栈。

局部变量区: 用来存放方法中的所有局部变量值,包括传递的参数。这些数据会被组织成以一个字长(32bit或64bit)为单位的数组结构(以索引0开始)中。其中类 型为int, float, reference(引用类型,记录对象在堆中地址)和returnAddress(一种JVM内部使用的基本类型)的值占用1个字长,而byte, char和shot会扩大成1个字长存储,long,double则使用2个字长。

 操作数栈: 用来在执行指令的时候存储和使用中间结果数据。

帧数据区: 常量池的解析,正常方法返回以及异常派发机制的信息数据都存储在其中。

3)本地方法栈(线程私有)

与Java虚拟机栈类似,只不过该区域是为native方法提供服务。

4)方法区(Perm)(线程共享)

    存储已被虚拟机加载的类信息,常量,静态变量,即时编译后的代码等数据。包含运行时常量池,用于存放编译器生成的各种字面量和符号引用,这部分内容是在类加载后进入方法区运行时常量池中。

5)堆

wKiom1U3lKGQmJtcAAGiCyI3bMk066.jpg


堆是整个内存数据区最负责的部分,负责对象的创建。同时,垃圾回收的主要工作也在于此。堆又进一步进行细分,主要是为了满足垃圾回收。

堆的组成

Eden(伊甸园):对象创建的入口。

Survivor Space:用于保存在eden space内存池中经过垃圾回收后没有被回收的对象,也就是“幸存还活着”的对象。

幸存者0区(Survivor 0 space)和幸存者1区(Survivor1 space):当伊甸园的空间用完时,程序又需要创建对象;此时JVM的垃圾回收器将对伊甸园区进行垃圾回收,将伊甸园区中的不再被其他对象所引用的对象 进行销毁工作。同时将伊甸园中的还有其他对象引用的对象移动到幸存者0区。幸存者0区就是用于存放伊甸园垃圾回收时所幸存下来的JAVA对象。

当将伊甸园中的还有其他对象引用的对象移动到幸存者0区时,如果幸存者0区也没有空间来存放这些对象时,JVM的垃圾回收器将对幸存者0区进行垃圾 回收处理,将幸存者0区中不在有其他对象引用的JAVA对象进行销毁,将幸存者0区中还有其他对象引用的对象移动到幸存者1区。幸存者1区的作用就是用于 存放幸存者0区垃圾回收处理所幸存下来的JAVA对象。


Tenured :对象经过survivor 1 space内存池,每经历过一次垃圾回收,年龄就增加1,超过设定阀值后,被移入终身代,当然也包括由于担保机制移入的对象。

对于新生代和老年代,垃圾回收器对其态度不同。

发生在新生代的回收频率频繁,大部分对象是“朝生夕死”,收集算法一般采用高效简单的复制算法,也就是上文描述的对象转移操作(Eden->survivor 0,survivor 0->survivor 1)。发生在该区域的垃圾回收为Young GC.

对于老年代,由于大部分对象主要为存活率高的对象,垃圾回收器采用”标记-整理“算法。发生在该区域的垃圾回收为FULL GC.


堆相关参数

(影响堆空间划分,进而会影响GC发生频率)JVM调优工作,主要是基于这些参数,进行适当调整管理,达到调整堆内存大小及比例大小,以满足实际业务需求。另外还包括方法区。

-Xms:设置 Java 应用程序启动时的初始堆大小;

-Xmx:设置 Java 应用程序能获得的最大堆大小;

-Xss:设置线程栈的大小;

-XX:MinHeapFreeRatio:设置堆空间最小空闲比例。当堆空间的空闲内存小于这个数值时,JVM 便会扩展堆空间;

-XX:MaxHeapFreeRatio:设置堆空间的最大空闲比例。当堆空间的空闲内存大于这个数值时,便会压缩堆空间,得到一个较小的堆;

-XX:NewSize:设置新生代的大小;

-XX:NewRatio:设置老年代与新生代的比例,它等于老年代大小除以新生代大小;

-XX:SurvivorRatio:新生代中 eden 区与 survivor 区的比例;

-XX:MaxPermSize:设置最大的持久区大小;

-XX:TargetSurvivorRatio: 设置 survivor 区的可使用率。当 survivor 区的空间使用率达到这个数值时,会将对象送入老年代。


对象的生命周期

  创建阶段

1)检查指令的参数,是否能在常量池中定位到一个类的符号引用,如果是引用,判断代表的类是否加载,解析和初始化过

2)如果没有加载,则必须进行加载,解析和初始化

3)类加载检查,这时候已经知道所需内存的大小。

4)分配内存。从java堆中划分一块大小确定的内存。支持2种方式,至于选择哪种方式分配内存,与java堆是否规整有关(也就是是否空间空间和使用空间相互交错情况)。1.指针碰撞(分界点的指示器移动);2.空闲列表方式。然而,java堆是否规整,则取决于垃圾收集器的工作方式。此外,在分配内存时还要考虑多线程情况,保证原子性。分配内存的原子性有2种方式进行保证(CAS 和 本地线程分配缓冲-XX +/- UseTLAB)。

5) 分配内存完成后,初始化内存空间(初始化为0)

6)维护对象的对象头信息。如元数据信息,哈希码,GC分代年龄,锁信息,类元指针。

7)调用init方法,按照程序员意愿进行初始化。

     <7.1> 从超类到子类对static成员进行初始化; 
     <7.2> 超类成员变量按顺序初始化,递归调用超类的构造方法; 
      
 <7.3> 子类成员变量按顺序初始化,子类构造方法调用。

应用阶段

分为强引用、软引用、虚引用、若引用

   不可视阶段; 

当一个对象处于不可视阶段,说明我们在其他区域的代码中已经不可以在引用它,其强引用已经消失,例如,本地变量超出了其可视的范围。 

   不可到达阶段; 

处于JVM对象生命周期不可到达阶段的对象,在虚拟机所管理的对象引用根集合中再也找不到直接或间接的强引用,这些对象通常是指所有线程栈中的临时变量, 所有已装载的类的静态变量或者对本地代码接口(JNI)的引用。这些对象都是要被垃圾回收器回收的预备对象,但此时该对象并不能被垃圾回收器直接回收。其 实所有垃圾回收算法所面临的问题是相同的——找出由分配器分配的,但是用户程序不可到达的内存块。

   可收集阶段、终结阶段、释放阶段 ; 

当一个对象处于可收集阶段、终结阶段与释放阶段时

     <1> 回收器发现该对象已经不可达。 
     <2> finalize方法已经被执行。 
     <3> 对象空间已被重用。



本文出自 “简单” 博客,请务必保留此出处http://dba10g.blog.51cto.com/764602/1637276

目录
相关文章
|
20天前
|
缓存 算法 关系型数据库
MySQL底层概述—1.InnoDB内存结构
本文介绍了InnoDB引擎的关键组件和机制,包括引擎架构、Buffer Pool、Page管理机制、Change Buffer、Log Buffer及Adaptive Hash Index。
198 97
MySQL底层概述—1.InnoDB内存结构
|
11天前
|
存储 算法 Java
JVM: 内存、类与垃圾
分代收集算法将内存分为新生代和老年代,分别使用不同的垃圾回收算法。新生代对象使用复制算法,老年代对象使用标记-清除或标记-整理算法。
18 3
|
12天前
|
安全 C语言 C++
彻底摘明白 C++ 的动态内存分配原理
大家好,我是V哥。C++的动态内存分配允许程序在运行时请求和释放内存,主要通过`new`/`delete`(用于对象)及`malloc`/`calloc`/`realloc`/`free`(继承自C语言)实现。`new`分配并初始化对象内存,`delete`释放并调用析构函数;而`malloc`等函数仅处理裸内存,不涉及构造与析构。掌握这些可有效管理内存,避免泄漏和悬空指针问题。智能指针如`std::unique_ptr`和`std::shared_ptr`能自动管理内存,确保异常安全。关注威哥爱编程,了解更多全栈开发技巧。 先赞再看后评论,腰缠万贯财进门。
|
3月前
|
Rust 安全 Java
JVM原理与实现——Synchronized关键字
在多线程Java程序中,`Synchronized`关键字用于确保线程安全。本文深入探讨其工作原理,通过分析字节码`monitorenter`和`monitorexit`,解释JVM如何实现同步机制。文章展示了`Synchronized`方法的编译结果,并详细解析了轻量锁和重度锁的实现过程,包括Mark Word的状态变化及CAS操作的应用。最后简要介绍了`ObjectMonitor::enter()`函数在获取重度锁时的作用。
JVM原理与实现——Synchronized关键字
|
3月前
|
监控 架构师 Java
Java虚拟机调优的艺术:从入门到精通####
本文作为一篇深入浅出的技术指南,旨在为Java开发者揭示JVM调优的神秘面纱,通过剖析其背后的原理、分享实战经验与最佳实践,引领读者踏上从调优新手到高手的进阶之路。不同于传统的摘要概述,本文将以一场虚拟的对话形式,模拟一位经验丰富的架构师向初学者传授JVM调优的心法,激发学习兴趣,同时概括性地介绍文章将探讨的核心议题——性能监控、垃圾回收优化、内存管理及常见问题解决策略。 ####
|
3月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
4月前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
4月前
|
监控 Java 编译器
Java虚拟机调优指南####
本文深入探讨了Java虚拟机(JVM)调优的精髓,从内存管理、垃圾回收到性能监控等多个维度出发,为开发者提供了一系列实用的调优策略。通过优化配置与参数调整,旨在帮助读者提升Java应用的运行效率和稳定性,确保其在高并发、大数据量场景下依然能够保持高效运作。 ####
60 1
|
4月前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
40 3
|
4月前
|
存储 算法 Java
JVM进阶调优系列(10)敢向stop the world喊卡的G1垃圾回收器 | 有必要讲透
本文详细介绍了G1垃圾回收器的背景、核心原理及其回收过程。G1,即Garbage First,旨在通过将堆内存划分为多个Region来实现低延时的垃圾回收,每个Region可以根据其垃圾回收的价值被优先回收。文章还探讨了G1的Young GC、Mixed GC以及Full GC的具体流程,并列出了G1回收器的核心参数配置,帮助读者更好地理解和优化G1的使用。

相关实验场景

更多