JVM运行数据区加载.class文件及new一个对象的过程,JVM运行时数据区

JVM创建对象过程

  1. new一个实例,如new People()
  • 到Class文件信息的常量池中检查是否有People这个类的符号引用,没有就执行【类加载过程】

  • 为People对象去堆分配内存(分配的大小已在类加载过程中确定),默认分到Eden区,进行一次GC后没被回收才转移到Survivor区。

    • 两种分配方式:指针碰撞(堆规整,直接内存整移一份当前对象实例大小即可)、空闲列表(堆不规整,用列表记录那些内存可用)
    • 注意点:带有压缩功能的垃圾收集器(如Serial、ParNew)才使java堆规整
  • 为实例分配内存空间时,通过CAS同步、本地线程缓存这两种确保线程安全性

  • 内存分配完后,将分配到的内存空间都为零,不包含对象头

  • 执行方法,把对象按程序员意愿初始化比如构造函数,如private int i = 1;此时就真的把1赋给 i。

2 再说说[加载、验证、准备、解析、初始化、使用、卸载]七个阶段:
2.1 jvm加载.class过程:
1 加载(使用类加载器-详情见我其他博客的双亲委派模式/当前类加载器/线程上下文类加载器):

  • 1.1 通过类全限定名获取该类二进制字节流。
  • 1.2 将字节流转为方法区的运行时数据结构。
  • 1.3 在方法区生成对应Class对象(存在Class文件信息中),作为该类的各种数据入口。
    2 验证:验证.class文件是否符合jvm要求,如魔数开头、版本、字节码、符号引用等(这些均在编译期已生成)
    3 准备:初始化类变量(如static int i; => 初始化为0),放到运行时常量池里,如果定义如下则直接赋好值"5"如下例子:
  • private static final i = 5; 运行时常量池直接给i赋值5。
  • 3.1 将字符串对象的实例引用值存到String Pool。
    4 解析:将常量池中的符号引用转为直接引用(该阶段也可以是初始化后,如动态绑定),期间会与String Pool作比较
    5 初始化:执行到了阶段,初始化静态变量赋值,然后初始化静态代码块。会先初始化当前类的parent,及最先赋值java.lang.Object的类变量(如果有的话)

Java内存管理:Java内存区域 JVM运行时数据区

Java虚拟机规范定义了字节码执行期间使用的各种运行时数据区,即JVM在执行Java程序的过程中,会把它管理的内存划分为若干个不同的数据区域,包括:

  程序计数器、java虚拟机栈、本地方法栈、java堆、方法区、运行时常量池;

   从线程共享角度来说,可以分为两类:

1、所有线程共享的数据区

   方法区、运行时常量池、java堆;

   这些数据区域是在Java虚拟机启动时创建的,只有当Java虚拟机退出时才会被销毁;

2、线程间隔离的数据区

   程序计数器、java虚拟机栈、本地方法栈、

   这些数据区域是每个线程的"私有"数据区,每个线程都有自己的,不与其他线程共享;

   每个线程的数据区在创建线程时创建,并在线程退出时被销毁;

3、另外,还一种特殊的数据区

  直接内存--使用Native函数库直接分配的堆外内存;

   即Java内存区域 = JVM运行时数据区 +直接内存。

2、Java各内存区域说明
上面图片展示的是JVM规范定义的运行时数据概念模型,实际上JVM的实现可能有所差别,下面在介绍各内存数据区时会给出一些HotSpot虚拟机实现的不同点和调整参数。

2-1、程序计数器
程序计数器(Program Counter Register),简称PC计数器;

1、生存特点

   每个线程都需要一个独立的PC计数器,生命周期与所属线程相同,各线程的计数器互不影响;

2、作用

  JVM字节码解释器通过改变这个计数器的值来选取线程的下一条执行指令;

3、存储内容

   JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的;

   在任意时刻,一个线程只会执行一个方法的代码(称为该线程的当前方法(Current Method));        

(A)、如果这个方法是Java方法,那PC计数器就保存JVM正在执行的字节码指令的地址;

(B)、如果该方法是native的,那PC计数器的值是空(undefined);

4、内存分配特点

   PC计数器占用较小的内存空间;

   容量至少应当能保存一个returnAddress类型的数据或者一个与平台相关的本地指针的值;

5、异常情况

   唯一一个JVM规范中没有规定会抛出OutOfMemoryError情况的区域;

2-2、Java虚拟机栈
Java虚拟机栈(Java Virtual Machine Stack,JVM Stack),指常说的栈内存(Stack);

   和Java堆指的堆内存(Heap),都是需要重点关注的内存区域;

1、生存特点

   每个线程都有一个私有的,生命周期与所属线程相同;

2、作用

   描述的是Java方法执行的内存模型,与传统语言中(如C/C++)的栈类似;

   在方法调用和返回中也扮演了很重要的角色;

3、存储内容

   用于保存方法的栈帧(Stack Frame);

   每个方法从调用到执行结束,对应其栈帧在JVM栈上的入栈到出栈的过程;

       栈帧:

   每个方法执行时都会创建一个栈帧,随着方法调用而创建(入栈),随着方法结束而销毁(出栈);

   栈帧是方法运行时的基础结构;

   栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息;

(A)、局部变量表

   局部变量表(Local Variables Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。

   这些都是在编译期可知的数据,所以一个方法调用时,在JVM栈中分配给该方法的局部变量空间是完全确定的,运行中不改变;

   一个方法分配局部变量表的最大容量由Class文件中该方法的Code属性的max_locals数据项确定;

(B)、操作数栈

   操作数栈(Operand Stack)简称操作栈,它是一个后进先出(Last-In-First-Out,LIFO)栈;

   在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容(任意类型的值),也就是入栈/出栈操作;

   在方法调用的时候,操作数栈也用来准备调用方法的参数以及接收方法返回结果;

   一个方法的操作数栈长度由Class文件中该方法的Code属性的max_stacks数据项确定;

(C)、动态链接

   每一个栈帧内部都包含一个指向运行时常量池的引用,来支持当前方法的执行过程中实现动态链接 (Dynamic Linking);

   在 Class 文件里面,描述一个方法调用了其他方法,或者访问其成员变量是通过符号引用(Symbolic Reference)来表示的;

  动态链接的作用就是将这些符号引用所表示的方法转换为实际方法的直接引用(除了在类加载阶段解析的一部分符号);

4、内存分配特点

   因为除了栈帧的出栈和入栈之外,JVM栈从来不被直接操作,所以栈帧可以在堆中分配;

   JVM栈所使用的内存不需要保证是连续的;

   JVM规范允许JVM栈被实现成固定大小的或者是根据计算动态扩展和收缩的:

(A)、固定大小

   如果JVM栈是固定大小的,则当创建新线程的栈时,可以独立地选择每个JVM栈的大小;

(B)、动态扩展或收缩

   在动态扩展或收缩JVM栈的情况下,JVM实现应该提供调节JVM栈最大和最小内存空间的手段;

两种情况下,JVM实现都应当提供调节JVM栈初始内存空间大小的手段;

  HotSpot VM通过"-Xss"参数设置JVM栈内存空间大小;

5、异常情况

   JVM规范中对该区域,规定了两种可能的异常状况:

(A)、StackOverflowError

   如果线程请求分配的栈深度超过JVM栈允许的最大深度时,JVM将会抛出一个StackOverflowError异常;

(B)、 OutOfMemoryError

   如果JVM栈可以动态扩展,当然扩展的动作目前无法申请到足够的内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的虚拟机栈,那JVM将会抛出一个OutOfMemoryError异常;

该区域与方法执行的JVM字节码指令密切相关,这里篇幅有限,以后有时间会分析方法的调用与执行过程,再来详细介绍该区域。

2-3、本地方法栈
本地方法栈(Native Method Stack)与 Java虚拟机栈类似;

1、与Java虚拟机栈的区别

   Java虚拟机栈为JVM执行Java方法(也就是字节码)服务;

   本地方法栈则为Native方法(指使用Java以外的其他语言编写的方法)服务;

2、HotSpot VM实现方式

   JVM规范中没有规定本地方法栈中方法使用的语言、方式和数据结构,JVM可以自由实现;

  HotSpot VM直接把本地方法栈和Java虚拟机栈合并为一个;

2-4、Java堆
Java堆(Java Heap)指常说的堆内存(Heap);

1、生存特点

   所有线程共享;

   生命周期与JVM相同;

2、作用

  为"new"创建的实例对象提供存储空间;

   里面存储的这些对象实例都是通过垃圾收集器(Garbage Collector)进行自动管理,所以Java堆也称"GC堆"(Garbage Collected Heap);

对GC堆以及GC的参数设置调整,就是JVM调优的主要内容;

3、存储内容

  用于存放几乎所有对象实例;

   (随JIT编译技术和逃逸分析技术发展,少量对象实例可能在栈上分配,详见后面介绍JIT编译的文章);

4、内存分配特点

(A)、Java堆划分

   为更好回收内存,或更快分配内存,需要对Java堆进行划分:

(I)、从垃圾收集器的角度来看

   JVM规范没有规定JVM如何实现垃圾收集器;

   由于很多JVM采用分代收集算法,所以Java堆还可以细分为:新生代、老年代和永久代;

(II)、从内存分配角度来看

   为解决分配内存线程不安全问题,需要同步处理;

   Java堆可能划分出每个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),减少线程同步;

HotSpot VM通过"-XX:+/-UseTLAB"指定是否使用TLAB;

(B)、分配调整

   和JVM栈一样,Java堆所使用的物理内存不需要保证是连续的,逻辑连续即可;

   JVM规范允许Java堆被实现成固定大小的或者是根据计算动态扩展和收缩的:

   两种情况下,JVM实现都应当提供调节JJava堆初始内存空间大小的手段;

   在动态扩展或收缩的情况下,还应该提供调节最大和最小内存空间的手段;

(C)、HotSpot VM相关调整

   目前主流的JVM都把Java堆实现成动态扩展的,如HotSpot VM:

(1)、初始空间大小

   通过"-Xms"或"-XX:InitialHeapSize"参数指定Java堆初始空间大小;

   默认为1/64的物理内存空间;

(2)、最大空间大小

   通过"-Xmx"或"-XX:MaxHeapSize"参数指定ava堆内存分配池的最大空间大小;

   默认为1/4的物理内存空间;

   Parallel垃圾收集器默认的最大堆大小是当小于等于192MB物理内存时,为物理内存的一半,否则为物理内存的四分之一;

(3)、各年代内存的占用空间与可用空间的比例

   通过"-XX:MinHeapFreeRatio"和"-XX:MaxHeapFreeRatio"参数设置堆中各年代内存的占用空间与可用空间的比例保持在特定范围内;

   默认:

   "-XX:MinHeapFreeRatio=40":即一个年代(新生代或老年代)内存空余小于40%时,JVM会从未分配的堆内存中分配给该年代,以保持该年代40%的空余内存,直到分配完"-Xmx"指定的堆内存最大限制;

   "-XX:MaxHeapFreeRatio=70":即一个年代(新生代或老年代)内存空余大于70%时,JVM会缩减该年代内存,以保持该年代70%的空余内存,直到缩减到"-Xms"指定的堆内存最小限制;

  这两个参数不适用于Parallel垃圾收集器(通过“-XX:YoungGenerationSizeIncrement”、“-XX:TenuredGenerationSizeIncrement ”能及“-XX:AdaptiveSizeDecrementScaleFactor”调节);

(4)、年轻代与老年代的大小比例

   通过"-XX:NewRatio":控制年轻代与老年代的大小比例;

   默认设置"-XX:NewRatio=2"表新生代和老年代之间的比例为1:2;

   换句话说,eden和survivor空间组合的年轻代大小将是总堆大小的三分之一;

(5)、年轻代空间大小

   通过"-Xmn"参数指定年轻代(nursery)的堆的初始和最大大小;

   或通过"-XX:NewSize"和"-XX:MaxNewSize"限制年轻代的最小大小和最大大小;

(6)、定永久代空间大小

   通过"-XX:MaxPermSize(JDK7)"或"-XX:MaxMetaspaceSize(JDK8)"参数指定永久代的最大内存大小;

   通过"-XX:PermSize(JDK7)"或"-XX:MetaspaceSize(JDK8)"参数指定永久代的内存阈值--超过将触发垃圾回收;

   注:JDK8中永久代已被删除,类元数据存储空间在本地内存中分配;

参考文章:
https://mp.weixin.qq.com/s/rLooaTOU_NQTJdn28KAUFw
https://blog.csdn.net/qq_28666081/article/details/85346470
https://www.cnblogs.com/alsf/p/9484807.html

你可能感兴趣的:(java)