JVM内存模型

133 阅读5分钟

M内存模型

JDK

JRE(JAVA运行时环境)----支撑Java程序运行的核心类库,包括一些常用的工具

Libraries

  • lang and util
  • Collection
  • Concurrency Utils
  • Input/Output
  • Math
  • ......

JVM(Java Virtual Machine)

实现Java语言的跨平台 内存模型

image.png

  • 内存地址----对象的指针

    • 年轻代:占堆大小的1/3

      • Eden(伊甸区)---8/10
      • survive from区----1/10
      • survicve to区----1/10
    • 老年代:占堆大小的2/3

    • Full GC回收的时堆以及整个方法区

  • 方法区

    • 常量、静态变量、类元信息
  • 栈(线程)页可以存放new出来的对象

    • 用来放线程内部的局部变量,不同的线程执行相同的代码生成的局部变量也是不一样的

    • 一个方法对应一个栈帧内存空间

    • 局部变量

    • 操作数栈

      • 存放需要 进行局部变量之简单相关的运算的值,比如:int c = (a + b)*10,要执行加法和乘法,则从操作数栈中取出a,b的值-----底层由cpu进行操作
    • 方法出口

      • 当前方法运行完成之后,返回到main方法中继续往哪个方法执行
    • 动态链接

      • 在程序运行的过程中将符号引用转换为直接引用---涉及c语言
  • 虚拟机栈/本地方法栈

Java程序中的Native方法

  • 程序计数器

记录程序下一步运行的指令地址/行号----为多线程准备

与堆有关垃圾回收

image.png 上面已经提到堆中分为年轻代和老年代,他们之间的占比是1:2,年轻代又分为Eden区、from Survivor区、to Survivor区,这三个区域在年轻代中的占比是8:1:1。对象首先在堆中Eden区分配,第一次minorGC存活的对象移动到from Survivor区,在进行第二次垃圾回收时候,同时清理Eden区和from survivor区的垃圾对象,剩余存活的对象移动到to survivor区。每移动一次对象的年龄+1,当对象的年龄达到15的时候,也就是经过15次的minorGC,还存活的对象会移动到老年代。老年代放满了之后会触发full GC----回收堆以及整个方法区 fullgc之后没有足够的空间存放new出来的对象就会出现OOM

在一个web应用系统中静态变量引用的变量、对象池、缓存对象、spring容器里面的对象都有可能挪到老年代中。

JvisualVM----JDK自带jvm诊断调优工具

//死循环不断地往里面加对象----new HeapTest()
public class HeapTest{
        byte[] a = new byte[1024 * 100];
        public static void main(String[] args){
        ArrayList<HeapTest> heapTests = new ArrayList<>();
        while(true){
            heapTests.add(new HeapTest());
            Thread.sleep(100);
        }
        
        }

}

一般自己测试调试的时候用的比较多,线上使用Arthus比较多

JVM内存参数设置

主要设置这三个区域

image.png 方法区/元空间默认的初始值是21M,web应用程序很多的类信息都放到方法区中,达到了21兆就会触发fullGC

java -Xms2048M -Xmx2048M -Xmn1024M -Xss512K -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -jar microservice-eureka-server.jar

-Xss:每个线程的栈大小

-Xms:设置堆的初始可用大小,默认物理内存的1/64

-Xmx:设置堆的最大可用大小,默认物理内存的1/4

-Xmn:新生代大小

-XX:NewRatio:默认2表示新生代占年老代的1/2,占整个堆内存的1/3。

-XX:SurvivorRatio:默认8表示一个survivor区占用1/8的Eden内存,即1/10的新生代内存。

关于元空间的JVM参数有两个:-XX:MetaspaceSize=N和 -XX:MaxMetaspaceSize=N

-XX:MaxMetaspaceSize: 设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小。

-XX:MetaspaceSize: 指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M左右,达到该值就会触发full gc进行类型卸载, 同时收集器会对该值进行调整: 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间, 那么在不超过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。这个跟早期jdk版本的-XX:PermSize参数意思不一样,-XX:PermSize代表永久代的初始容量。

由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,对于8G物理内存的机器来说,一般可以将这两个值都设置为256M。

// JVM设置  -Xss128k(默认1M) 
public class StackOverflowTest {
static int count = 0;
static void redo() { 
count++; redo(); 
} 
public static void main(String[] args) { 
try {
redo(); 
} catch (Throwable t) { 
t.printStackTrace(); System.out.println(count); }
} 
} 
//运行结果: java.lang.StackOverflowError at com.tuling.jvm.StackOverflowTest.redo(StackOverflowTest.java:12) at com.tuling.jvm.StackOverflowTest.redo(StackOverflowTest.java:13) at com.tuling.jvm.StackOverflowTest.redo(StackOverflowTest.java:13) ......

结论: 线程栈和方法区用的都是操作系统的直接内存,-Xss单个栈的空间设置的越小,对程序来说能够开更多的额线程数。 栈的多少不能设置,根据还剩下多少内存决定 线程结束,对应的占内存全部释放。