欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JVM总结

程序员文章站 2023-12-25 21:39:57
...

1.JVM内存模型

1.本地方法栈(Native)   调用c部分
2.程序计数器  (程序代码行数)  JVM支持多个线程同时运行,每个线程都有自己的程序计数器。倘若当前执行的是 JVM 的方法,则该寄存器中保存当前执行指令的地址;倘若执行的是native 方法,则PC寄存器中为空。
3.栈 stack :每个线程有一个私有的栈,随着线程的创建而创建。栈里面存着的是一种叫“栈帧”的东西,每个方法会创建一个栈帧,栈帧中存放了局部变量表(基本数据类型和对象引用)、操作数栈、方法出口等信息。栈的大小可以固定也可以动态扩展。当栈调用深度大于JVM所允许的范围,会抛出*Error的错误
4.堆  heap(存实际对象) 堆内存是 JVM 所有线程共享的部分,在虚拟机启动的时候就已经创建。所有的对象和数组都在堆上进行分配。这部分空间可通过 GC 进行回收。当申请不到空间时会抛出 OutOfMemoryError
5方法区  :主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。 关于方法区内存溢出的问题会在下文中详细探讨

2.ClassLoader双亲委派机制 可以保护jvm自己的安全

   双亲委派机制 : 一层一层的让父类去加载,如果顶层的加载器不能加载,然后再向下类推
 // BootStrap (最顶层) 01 java.lang.String rt.jar   /jdk/jre/lib/ 下面的jar
 // ExtClassLoader 02     /jdk/jre/lib/ext/下面的jar
  // AppClassLoader 03   //AppClassLoader
     // 双亲委派机制 可以保护java的核心类不会被自己定义的类所替代
<wiz_code_mirror>
 
 
 
 
 
1
// -XX:+TraceClassLoading
2
public static void main(String[] args) {
3
        Object o = new Object(); // jdk 自带的
4
        Demo01 demo01 = new Demo01();  // 实例化一个自己定义的对象
5
        // null 在这里并不代表没有,只是Java触及不到!Bootstrap
6
        System.out.println(o.getClass().getClassLoader()); // null
7
        System.out.println(demo01.getClass().getClassLoader()); // AppClassLoader
8
        System.out.println(demo01.getClass().getClassLoader().getParent()); // ExtClassLoader
9
        System.out.println(demo01.getClass().getClassLoader().getParent().getParent()); // null
10
    }
 
 
<wiz_code_mirror>
 
 
 
 
 
1
 // 思考:为什么我们刚才自己定义的 java.lang.String 没有生效?
2
 //Bootstrap 优先加载 没找到main方法  双亲委派机制 一层层加载
3
public class String {
4
    public static void main(String[] args) {
5
        System.out.println("hello");
6
    }
7
}
 
 
 
<wiz_code_mirror>
 
 
 
 
 
1
public class Demo01 {
2
    private static int m=0;
3
    public static void main(String[] args) {
4
        System.out.println(Parent.str);//没加会输出static init1 str如果加了final不会输出staticinit  没加final会先输出static init
5
        System.out.println(Parent2.str);
6
        Parent.kk();
7
    }
8
    static class Parent{
9
        public final static String str="hello";//会优先放常量池
10
        static  {
11
            System.out.println("static init1");
12
        }
13

14
        public static void kk() {
15
            m++;
16
            System.out.println(m);
17
            kk();
18
        }
19
    }
20
    static class Parent2{
21
        public static final String str=UUID.randomUUID().toString();//会输出静态代码块内容
22
        static  {
23
            System.out.println("static init2");
24
        }
25
    }
26
}
 
 

3.JDK1.8之后和1.7区别

JDK1.7之前 
永久代:用于存储一些虚拟机加载类信息,常量,字符串、静态变量等等。。。。这些东西都会放到永久代中; 
永久代大小空间是有限的:如果满了 OutOfMemoryError:PermGen 
JDK1.8之后 
彻底将永久代移除 HotSpot jvm ,Java Heap 中或者 Metaspcace(Native Heap)元空间; 
元空间就是方法区在 HotSpot jvm 的实现; 
方法区重要就是来存:类信息,常量,字符串、静态变量、符号引用、方法代码。。。。。。 
元空间和永久代,都是对JVM规范中方法区的实现。 
元空间和永久代最大的区别:==元空间并不在Java虚拟机中,使用的是本地内存!== 
-XX:MetasapceSize10m 
 

4.方法区渊源

Method Area 方法区 是 Java虚拟机规范中定义的运行是数据区域之一,和堆(heap)一样可以在线程之间共享!栈不能共享

天上飞的理念都会有落地的实现!

JDK1.7之前

永久代:用于存储一些虚拟机加载类信息,常量,字符串、静态变量等等。。。。这些东西都会放到永久代中;

永久代大小空间是有限的:如果满了 OutOfMemoryError:PermGen

JDK1.8之后

彻底将永久代移除 HotSpot jvm ,Java Heap 中或者 Metaspcace(Native Heap)元空间;

元空间就是方法区在 HotSpot jvm 的实现;

方法区重要就是来存:类信息,常量,字符串、静态变量、符号引用、方法代码。。。。。。

元空间和永久代,都是对JVM规范中方法区的实现。

元空间和永久代最大的区别:==元空间并不在Java虚拟机中,使用的是本地内存!==

-XX:MetasapceSize10m

5.堆内存和GC过程

Java7之前: 
Heap 堆,一个JVM实例中只存在一个堆,堆的内存大小是可以调节的。 
可以存的内容:类、方法、常量、保存了类型引用的真实信息; 
分为三个部分: 
新生区:Young (Eden-s0-s1)  Eden-s0-s1默认8:1:1
养老区:Old Tenure   大对象(-XX:PretenureSizeThreadshold=1024)直接进入老年代,在新生代里面长期存活的对象进入老年代。老年代对应的是major gc。
新生区和养老区默认1:2
永久区:Perm 
堆内存在==逻辑上==分为三个部分:新生、养老、永久(JDK1.8以后,叫元空间) 
物理上只有 新生、养老;元空间在本地内存中,不在JVM中! 
GC 垃圾回收主要是在 新生区和养老区,又分为 普通的GC 和 Full GC,如果堆满了,就会爆出 OutOfMemory; 
新生区 
新生区 就是一个类诞生、成长、消亡的地方! 
新生区细分: Eden、s(from to),所有的类Eden被 new 出来的,慢慢的当 Eden 满了,程序还需要创建对象的时候,就会触发一次轻量级GC;清理完一次垃圾之后,会将活下来的对象,会放入幸存者区(),....... 清理了 20次之后,出现了一些极其顽强的对象,有些对象突破了15次的垃圾回收!这时候就会将这个对象送入养老区!运行了几个月之后,养老区满了,就会触发一次 Full GC;假设项目1年后,整个空间彻彻底底的满了,突然有一天系统 OOM,排除OOM问题,或者重启; 
Sun HotSpot 虚拟机中,内存管理(分代管理机制:不同的区域使用不同的算法!) 
Eden from to 
99% 的对象在 Eden 都是临时对象; 
养老区 
15次都幸存下来的对象进入养老区,养老区满了之后,触发 Full GC 
默认是15次,可以修改! 
永久区(Perm) 
放一些 JDK 自身携带的 Class、Interface的元数据; 
几乎不会被垃圾回收的; 
OutOfMemoryError:PermGen 在项目启动的时候永久代不够用了?加载大量的第三方包! 
JDK1.6之前: 有永久代、常量池在方法区; 
JDK1.7:有永久代、但是开始尝试去永久代,常量池在堆中; 
JDK1.8 之后:永久代没有了,取而代之的是元空间;常量池在元空间中; 
闲聊:方法区和堆一样,是共享的区域,是JVM 规范中的一个逻辑的部分,但是记住它的别名 非堆 
元空间:它是本地内存! 

6.堆内存调优和常用参数(初识)

1.8之后permSize和MaxPermSize被-XX:MetaspaceSize和-XX:MaxMetaspaceSize取代
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
 -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
JVM总结
            
    
    博客分类: java jvm 
 

-XX:+PrintGCDetails:输出详细的GC处理日志

-XX:+PrintGCTimeStamps:输出GC的时间戳信息

-XX:+PrintGCDateStamps:输出GC的时间戳信息(以日期的形式)

-XX:+PrintHeapAtGC:在GC进行处理的前后打印堆内存信息

-Xloggc:(SavePath):设置日志信息保存文件

Minor GC ,Full GC 触发条件

Minor GC触发条件:当Eden区满时,触发Minor GC。

Full GC触发条件:

(1)调用System.gc时,系统建议执行Full GC,但是不必然执行

(2)老年代空间不足

(3)方法去空间不足

(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存

(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

Java堆:所有线程共享。虚拟机启动时创建。存放对象实力和数组。所占内存最大。分为新生代(Young区),老年代(Old区)。新生代分Eden区,Servior区。Servior区又分为From space区和To Space区。Eden区和Servior区的内存比为8:1。 当扩展内存大于可用内存,抛OOM。

 

GC流程  ,对象如何晋升到老年代

对象优先在新生代区中分配,若没有足够空间,Minor GC; 大对象(需要大量连续内存空间)直接进入老年态;长期存活的对象进入老年态。如果对象在新生代出生并经过第一次MGC后仍然存活,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。

 

  1. 【对象优先在Eden中分配,当Eden中没有足够的空间分配时会促发一次Minor GC。每次Minor GC结束后,Eden区会清空,因为它会把Eden中还依然存活的对象放到Survivor中,当Survivor中放不下时,则由分派担保进入老年代中。
  2. 大对象直接进入老年代中。-XX:+PretenuerSizeThreshold 控制”大对象的“的大小。即当创建的对象大于这个临界值时,则该对象直接进入老年代。
  3. 长期存活的对象将进入老年代。虚拟机对每个对象定义了一个对象年龄(Age)计数器。当年龄增加到一定的临界值时,就会晋升到老年代中,该临界值由参数:-XX:MaxTenuringThreshold来设置。如果对象在Eden出生并在第一次发生Minor GC时仍然存活,并且能够被Survivor中所容纳的话,则该对象会被移动到Survivor中,并且设Age=1;以后每经历一次Minor GC,该对象还存活的话会被移动到另一个Survivor区中,并且Age=Age+1。
  4. 动态对象年龄判定:如上所示,虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代,如果在Survivor区中相同年龄(设年龄为age)的对象的所有大小之和超过Survivor空间的一半,年龄大于或等于该年龄(age)的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。】
老年代的垃圾回收称作Full GC。老年代所占用的内存大小为-Xmx对应的值减去-Xmn对应的值。
<wiz_code_mirror>
 
 
 
 
 
1
/**
2
 * 默认情况:
3
 * maxMemory : 1808.0MB (虚拟机试图使用的最大的内存量  一般是物理内存的 1/4)
4
 * totalMemory : 123.0MB (虚拟机试图默认的内存总量 一般是物理内存的 1/64)
5
 */
6
// 我们可以自定堆内存的总量
7
// -XX:+PrintGCDetails; // 输出详细的垃圾回收信息
8
// -Xmx: 最大分配内存; 1/4
9
// -Xms: 初始分配的内存大小; 1/64
10

11
// -Xmx1024m -Xms1024m -XX:+PrintGCDetails
12
public class Demo01 {
13
    public static void main(String[] args) {
14
        // 获取堆内存的初始大小和最大大小
15
        long maxMemory = Runtime.getRuntime().maxMemory();
16
        long totalMemory = Runtime.getRuntime().totalMemory();
17

18
        System.out.println("maxMemory="+maxMemory+"(字节)、"+(maxMemory/1024/(double)1024)+"MB");
19
        System.out.println("totalMemory="+totalMemory+"(字节)、"+(totalMemory/1024/(double)1024)+"MB");
20

21
    }
22
}
 
 
 
<wiz_code_mirror>
 
 
 
 
 
1
/*
2
 * -Xmx8m -Xms8m -XX:+PrintGCDetails
3
 *
4
 * 分析GC日志:
5
 *
6
 *  [Times: user=0.00 sys=0.00, real=0.00 secs]
7
 * 1、GC 类型  GC:普通的GC,Full GC :重GC
8
 * 2、1536K 执行 GC之前的大小
9
 * 3、504K  执行 GC之后的大小
10
 * 4、(2048K) young 的total大小
11
 * 5、0.0012643 secs 清理的时间
12
 * 6、user 总计GC所占用CPU的时间   sys OS调用等待的时间   real 应用暂停的时间
13
 *
14
 * GC :串行执行 STW(Stop The World)  并行执行   G1
15
*/
16

17
public class Demo02 {
18
    public static void main(String[] args) {
19
        System.gc(); // 手动唤醒GC(),等待cpu的调用
20
        String str = "ilovecoding";
21
        while (true){
22
            str += str
23
                    + new Random().nextInt(999999999)
24
                    + new Random().nextInt(999999999);
25
        }
26
        // 出现问题:java.lang.OutOfMemoryError: Java heap space
27
    }
28
}
 
 

GCDump分析内存泄露分析

安装JProfile
运行时加上参数:-XX:+HeapDumpOnOutOfMemoryError   生成的文件在项目根目录下/同级
相关标签: jvm

上一篇:

下一篇: