JVM学习笔记

JVM学习笔记

JVM体系结构

类加载器(ClassLoader)

java虚拟机自带加载器

  • 启动类加载器(Bootstrap) C++
  • 扩展类加载器(Extension)Java
  • 应用程序类加载器(AppClassLoader)

用户自定义加载器

  • Java.lang.ClassLoader 的子类,用户可以定制类的加载方式。

ClassLoader的双亲委派机制

  • 当一个类收到类加载请求,首先不会尝试自己去加载这个类,而是把这个请求委派父类完成,每一层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才回去尝试自己加载。
  • 双亲委派的好处:
    • 保证使用不同的类加载器最终得到的都是同样一个Object对象。
      ClassLoader的沙箱安全机制

本地方法栈(Native Method Stack)

  • native 修饰的方法,会进 本地方法栈

程序计数器(Program Counter Register)

每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法去中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。

这块内存区域很小,它是当前线程所执行的字节码的行号指示器,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。

如果执行的是一个 Native 方法,那这个计数器是空的。

用以完成分支、循环、跳转、异常处理、线程回复等基础功能。不会发生内存溢出错误。

方法区(Method Area )

供各线程共享的运行时内存区域。它存储了每个类的结构信息,例如运行时常量池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容。
上面讲的是规范,在不同虚拟机里实现是不一样的,最典型的就是永久代(PermGen space)和元空间(Metaspace)。而实例变量是存在堆内存中的,和方法去无关。

栈(Stack)

栈管运行,堆管存储

队列:先进先出(FIFO)
栈:先进后出(FILO)

栈也叫栈内存,主管Java程序的运行,是在线程创建时创建它,它的生命周期是跟随线程的生命周期,线程结束栈内存也就释放了,对于栈来说不存在垃圾回收的问题,只要线程一结束该栈就结束,生命周期和线程是一直的,是线程私有的。
8种基本类型的变量 + 对象的引用变量 + 实例方法都是在函数的栈内存中分配的。

栈存储什么?
3类数据:

  • 本地变量(Local Variables):输入参数和输出参数以及方法内的变量;
  • 栈操作(Operand Stack):记录出栈、入栈的操作;
  • 栈帧数据(Frame Data):包括类文件、方法等。

堆(Heap)

一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量存放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行。

堆内存三部分:
物理上划分:新生 + 养老 两部分组成
逻辑上划分:新生 + 养老 + 永久 三部分组成

两个区的大小比例
YONG:Old = 1:2

新生区(Young/New)

三个分区的大小比例
YOUNG:FROM:TO = 8:1:1

1、伊甸园区(Eden Space)

Eden满了,开启GC,叫 Minor GC,轻GC
Eden基本全部清空

MinorGC过程(复制>>清空>>互换):

  • GC时,会杀Eden和From区,幸存的会拷贝到To区,幸存对象年龄+1,Eden和原From区被清空,装幸存者的To区变为From区,清空的From区变成To区;
  • 谁空谁变成To,部分对象会在GC过程中复制来复制去,交换15次(JVM参数 MaxTenuringThreshold决定,默认15)最终活下来的进入老年代
2、幸存者0区(Survivor 0 Space)

S0 = From 区

3、幸存者1区(Survivor 1 Space)

S1 = To 区

From 区和 To 区,位置和名份不是固定的,每次GC后会交换,谁空谁是To

养老区(Tenue Generation Space)

Old满了,开启GC,叫Major GC ,也叫Full GC
Full GC多次,养老区空间没法腾出来的时候,触发OOM

出现OOM说明 JVM 的堆内存不够,有两个原因:
(1)JVM堆内存设置不够,可通过参数 -Xms、-Xmx来调整;
(2)代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)

永久存储区(Permanent Space)(java8之后叫元空间)

永久代是方法区的一个实现,方法区和堆一样,是各个线程共享的内存区,它用于存储虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码等等,虽然JVM将方法区描述为堆的一个逻辑部分,但它却还有一个别名叫 Non-Heap(非堆),目的就是要和堆分开。
永久代不存在GC,用于存放JDK自身携带的class,interface的元数据,关闭jvm才会释放此区域。

堆参数调优

  • -Xms:堆内存初始值:默认为物理内存的“1/64”
  • -Xmx:堆内存最大值:默认为物理内存的"1/4"
  • -Xmn:新生区大小,一般不调
  • -XX:PermSize:永久代的初始值(java7)
  • -XX:MaxPermSize:永久代的最大值(java7)
  • -XX:+PrintGCDetails:输出详细的GC处理日志

jdk1.8后将最初的永久代取消,用元空间代替,元空间不在虚拟机中而是使用本机物理内存
元空间的大小仅受本地内存限制。

public static void main(String[] args){
    long maxMemory = Runtime.getRuntime().maxMemory();//返回java虚拟机视图使用的最大内存量。
    long totalMomory = Runtime.getRuntime().totalMemory();//返回java虚拟机中的内存总量。
    System.out.println("MAX_MEMORY = "+ maxMemory +"(字节)、" + (maxMemory/(double)1024/1024) +"MB");
    System.out.println("TOTAL_MEMORY = "+ totalMemory +"(字节)、" + (totalMemory/(double)1024/1024) +"MB");
}

实际设置堆内存参数时,将初始内存和最大内存设置一样,避免GC和应用程序争抢内存,资源争用导致内存不稳定;