JVM入门学习

一、关于JVM常见的面试题

  1. 对JVM的理解
  2. java8的JVM变化点
  3. OOM,栈溢出以及如何分析
  4. JVM调优常设置的参数
  5. 内存快照的获取以及分析Dump文件
  6. JVM中的类加载器

在这里插入图片描述

二、JVM概念
1)操作系统上一层是JVM(下一层是硬件),JVM可以理解为一个软件或者环境(java项目运行其中,JRE包含JVM),底层是用C编写;
2) 流程: .java文件---->.class文件 ---->类加载器class loader —>运行时数据区
在这里插入图片描述
其中99%的调优都是在堆heap,栈和程序计数器是不可能有垃圾的。
3)类加载器:
作用是加载.class文件
1)分为:虚拟机本身带有的加载器,根加载器(启动类加载器),拓展类加载器,应用程序加载器
注:jre里面的加载器或者jar包可以更改,但是不建议更改,如果改不好整个环境都会崩溃;

package com.byxrs.jvm;

/**
 * 1. Person.class --> Class Loader加载初始化 -->得到反射对象 Person class(可用于实例化)--->new 实例化
 * 2. 对象转为 Person class,采用getClass
 */
public class Person {
    public static void main(String[] args) {
        /*
        Class是模板对象,全局的,模板只有一个
        类是抽象的,对象是具体的
         */
        Class<Person> carClass = Person.class;
        /*
        p1  p2 引用存在栈中,new出来的对象存在堆中,通过内存地址产生对应的关系
         */
        Person p1 = new Person();
        Person p2 = new Person();
        // 2133927002
        System.out.println(p1.hashCode());
        // 1836019240
        System.out.println(p2.hashCode());
        /*
        getClass获得模板
         */
        Class<? extends Person> aClass1 = p1.getClass();
        Class<? extends Person> aClass2 = p2.getClass();
        // aClass1 aClass2是否一样?
        System.out.println(aClass1.hashCode());// 21685669
        System.out.println(aClass2.hashCode());// 21685669

        /*
        获取类加载器
         */
        ClassLoader classLoader = aClass1.getClassLoader();
        //sun.misc.Launcher$AppClassLoader@18b4aac2  应用程序加载器
        System.out.println(classLoader);
        //sun.misc.Launcher$ExtClassLoader@135fbaa4    jre1.8.0_231\lib\ext
        System.out.println(classLoader.getParent());
        //null 可能不存在,或者java程序获取不到根加载器rt.,jar  jre1.8.0_231\lib
        System.out.println(classLoader.getParent().getParent());
    }
}

延伸出来的内容:双亲委派机制(重点)
作用:保证安全
执行流程:
1)类加载器收到类加载的请求;
2)将请求一直向上2委托给父类加载器执行,直到启动类加载器。(APP加载器—ext加载器----boot根加载器);
3)启动类加载器检查能否加载这个类,能加载则结束,不能则抛出异常,让子加载器进行加载。
常见错误:ClassNotFound

3 沙箱安全机制(了解)
java安全的核心是沙箱(作用是限制系统资源的访问),沙箱就是限制程序运行的环境。沙箱机制既java代码在JVM特定运行范围中,并且访问本地系统资源也是有着严格的限制,保证了代码的有效隔离。
目前安全机制引入域Domain,JVM会把代码加载到不同的系统域(与关键资源交互)和应用域中,每个域有自己的权限。

package com.byxrs.jvm;

public class TestShaXiang {
    public static void main(String[] args) {
        new TestShaXiang().test1();
    }

    public void test1() {
        this.test2();
    }

    public void test2() {
        this.test1();
    }
}
运行结果:栈溢出了,是错误,而不是异常,错误会使得虚拟机停止运行。
Exception in thread "main" java.lang.StackOverflowError
	at com.byxrs.jvm.TestShaXiang.test1(TestShaXiang.java:15)
	at com.byxrs.jvm.TestShaXiang.test2(TestShaXiang.java:19)
	at com.byxrs.jvm.TestShaXiang.test1(TestShaXiang.java:15)
	at com.byxrs.jvm.TestShaXiang.test2(TestShaXiang.java:19)

沙箱组件:
1)字节码校验器,确保遵循规范
2)类装载器,可以防止恶意代码攻击我们的代码;可以守护被信任的类库边界;可以将代码归入保护域;
3)存取控制器
4)安全管理器

4 native 寄存器 方法区

native(重点)

package com.byxrs.jvm;


public class TestNative {
    public static void main(String[] args) {
        new Thread().start();
        /*
        Thread类中
        private native void start0();
        native关键字,说明java作用域达不到,需要调用底层C语言的库;
        会进入本地方法栈;
        会调用JNI本地方法接口(作用是拓展java功能,融合不同的语言为java使用,比如通过JNI调用python功能);
        产生背景:当时C  C++盛行,很多情况会需要调用C  C++写的程序
        在内存区域中专门开了一块标记区域(本地方法栈),登记native方法;
        执行时,通过JNI加载本地方法库中的方法
        应用场景:java链接打印机或者机械链接

        调用其他接口:socket,webservice,http
         */
    }
}

寄存器(了解):
每个线程都有一个程序计数器,是线程私有的,占有空间小;
方法区(重点):
所有线程共享,方法区存放静态变量static,常量final,类信息(构造方法,接口定义),运行时的常量池。实例变量存放在堆中,与方法区无关。

5.栈
5-1 是一种数据结构,先进后出(区别于队列–先进先出);
栈内存是管理程序的运行,生命周期和线程同步,线程结束,栈内存也就释放了,为此栈是不存在垃圾回收的情况。
比如执行程序,首先把main()方法压到栈中,然后放入不同的方法,执行方法则将方法拿出栈,最后拿出main()程序运行结束
5-1 栈存放8大基本类型,对象引用,实例方法
5-3 栈运行原理:栈帧

在这里插入图片描述
栈是怎么存数据?
对象实例化过程是什么样的?

6 堆
目前使用的JVM是sun公司的
一个JVM只有一个堆内存,大小是调整的;
堆一般是放类,方法,常量,变量,引用类型的真实对象,堆内存分为三个区(新生区(伊甸园区Eden,幸存0区和1区),养老区,永久区(元空间))。
GC垃圾回收分为 轻量级垃圾回收(针对新生区)和重量级垃圾回收(针对养老区)
在这里插入图片描述
新生区
类的产生,成长和死亡的区域
Eden:当Eden区满了之后会触发轻GC,后存活下来的数据会到幸存S0或者S1区
S0:与S1是动态交换的工作方法
S1:
老年区
当以上3个区数据都满了则会触发重GC来清楚3个区的数据,能存活下来的就会存在老年区中,当老年区满了则会报错OOM错误;
注:99%的数据是临时的,很少见到OOM;
元空间
java8以后常量池(方法区中,而方法区在元空间中)存放在这个区;
该区为常驻内存,用来存放JDK自身的class对象,存放运行时的环境。该区域不存在垃圾;
生命周期是关闭JVM。
注:1 一个启动类加载大量的第三方jar包可能导致内存满
2 还有就是一个tomcat部署太多的应用,动态生成大量的反射类,不断被加载,也容易内存满。
3 占用的空间比较小,可以被线程共享。
4 逻辑上存在,但是物理上不存在。

package com.byxrs.jvm;


public class JVMParam {
    public static void main(String[] args) {
        long maxMemory = Runtime.getRuntime().maxMemory();
        long totalMemory = Runtime.getRuntime().totalMemory();
        /*
        在Edit Configurations的VM Options不设置参数
        maxMemory:3563
        totalMemory:241
         */
        System.out.println("maxMemory:" +maxMemory/(double)1024 / 1024);
        System.out.println("totalMemory:" + totalMemory /(double) 1024 / 1024);

        //在Edit Configurations的VM Options设置: -Xms1024m -Xmx1024m -XX:+PrintGCDetails
        /*
        maxMemory:981.5
        totalMemory:981.5
        Heap
        PSYoungGen      total 305664K, used 20971K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
        eden space 262144K, 8% used [0x00000000eab00000,0x00000000ebf7afb8,0x00000000fab00000)
        from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
        to   space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
        ParOldGen       total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
        object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
        Metaspace       used 3239K, capacity 4496K, committed 4864K, reserved 1056768K
        class space    used 349K, capacity 388K, committed 512K, reserved 1048576K
         */

        /*
        面试:碰到OOM怎么办?
        假设电脑内存足够
        1.增加堆内存看结果,如果还是报错意味是代码的问题
        2.分析内存,查看哪个代码出问题(JProfile工具)
        JProfile工具作用:
        分析Dump内存文件,快速定位内存泄漏
        获取堆的数据
        获取大的对象
         */


    }
}

package com.byxrs.jvm;

import java.util.Random;

//设置参数  -Xms8m -Xmx8m -XX:+PrintGCDetails
public class OOMTest {
    public static void main(String[] args) {
        String line = "sdfghjklqwueito";
        while (true) {
            line += line + new Random().nextInt(666666666);
        }

        /**运行结果,先轻GC几次后重GC:
         * [GC (Allocation Failure) [PSYoungGen: 1534K->512K(2048K)] 1534K->734K(7680K), 0.0018363 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
         * [GC (Allocation Failure) [PSYoungGen: 1829K->511K(2048K)] 2051K->1308K(7680K), 0.0010115 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
         * [GC (Allocation Failure) [PSYoungGen: 1698K->512K(2048K)] 2495K->2265K(7680K), 0.0009816 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
         * [GC (Allocation Failure) [PSYoungGen: 1695K->512K(2048K)] 3449K->2647K(7680K), 0.0009032 secs] [Times: user=0.16 sys=0.02, real=0.00 secs]
         * [GC (Allocation Failure) [PSYoungGen: 1713K->512K(2048K)] 5379K->4185K(7680K), 0.0006835 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
         * [GC (Allocation Failure) [PSYoungGen: 1320K->512K(2048K)] 4993K->4950K(7680K), 0.0011169 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
         * [Full GC (Ergonomics) [PSYoungGen: 512K->0K(2048K)] [ParOldGen: 4438K->1415K(5632K)] 4950K->1415K(7680K), [Metaspace: 3154K->3154K(1056768K)], 0.0069688 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
         * [GC (Allocation Failure) [PSYoungGen: 796K->96K(2048K)] 3742K->3041K(7680K), 0.0005062 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
         * [GC (Allocation Failure) [PSYoungGen: 96K->64K(2048K)] 3041K->3009K(7680K), 0.0005389 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
         * [Full GC (Allocation Failure) [PSYoungGen: 64K->0K(2048K)] [ParOldGen: 2945K->2902K(5632K)] 3009K->2902K(7680K), [Metaspace: 3155K->3155K(1056768K)], 0.0090333 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
         * [GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 2902K->2902K(7680K), 0.0006158 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
         * [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 2902K->2883K(5632K)] 2902K->2883K(7680K), [Metaspace: 3155K->3155K(1056768K)], 0.0088098 secs] [Times: user=0.24 sys=0.00, real=0.01 secs]
         *
         * Heap
         *  PSYoungGen      total 2048K, used 121K [0x00000000ffd80000, 0x0000000100000000, 0x0000000100000000)
         *   eden space 1536K, 7% used [0x00000000ffd80000,0x00000000ffd9e4d8,0x00000000fff00000)
         *   from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
         *   to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
         *  ParOldGen       total 5632K, used 2883K [0x00000000ff800000, 0x00000000ffd80000, 0x00000000ffd80000)
         *   object space 5632K, 51% used [0x00000000ff800000,0x00000000ffad0c48,0x00000000ffd80000)
         *  Metaspace       used 3252K, capacity 4496K, committed 4864K, reserved 1056768K
         *   class space    used 351K, capacity 388K, committed 512K, reserved 1048576K
         *
         *  Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
         * 	at java.util.Arrays.copyOf(Arrays.java:3332)
         * 	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
         * 	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674)
         * 	at java.lang.StringBuilder.append(StringBuilder.java:208)
         * 	at com.byxrs.jvm.OOMTest.main(OOMTest.java:10)
         * */
    }
}

如何Dump文件(dump文件是JVM的快照)?需要安装软件和设置参数

package com.byxrs.jvm;


import java.util.ArrayList;

/**
 * -Xms 设置初始化内存分配大小 默认1/64
 * -Xmx 设置最大分配内存 默认1/4
 * -XX:+HeapDumpOnOutOfMemoryError     Dump文件
 * -XX:+PrintGCDetails    打印GC
 */
public class JProfileTest {
    byte[] array = new byte[1*1024*1024];

    public static void main(String[] args) {
        ArrayList<JProfileTest> jProfileList = new ArrayList<JProfileTest>();
        int count =0;
        try {
            while(true){
                jProfileList.add(new JProfileTest());
                count ++;
            }
        }catch (Error ex){
            System.out.println("count:"+count);
            ex.printStackTrace();
        }

        /*
        如何Dump文件?
        //设置参数  -Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
        再次运行后生成Dump文件  java_pid37420.hprof:位置在项目文件下 如 F:\IdeaProjects\spark
        java.lang.OutOfMemoryError: Java heap space
        Dumping heap to java_pid37420.hprof ...
        Heap dump file created [7738142 bytes in 0.015 secs]

        直接双击打开文件


         */


    }
}

总结:由于JVM分为新生带,幸存区和老年区,但是进行GC时,大部分还是在新生代。
GC又分为轻GC,重GC。
JVM的内存模型和分区是怎么样的,以及每个区是放什么东西?
堆的分区有哪些?Eden, from,to,老年区
GC算法常见的有标记清除法,标记整理,复制算法,引用计数法。
引用计数法:计数器本身会有消耗,每用一次对象计数器加1,但是这个方法不高效。
复制算法:目前主要用复制算法,流程是每次GC都会将Eden存活的对象移动到幸存区中,一旦Eden区被GC,就会变为空。当一个对象经历默认15次(该参数可以设定)GC后还是说存活的,就会进入老年区。优势是没有内存碎片,劣势是浪费了内存空间,有一半空白的空间是作为TO的,极端情况下,假设对象100%从新生代存活下来到幸存区to,to和from复制的时候会浪费大量的资源。
复制算法最佳使用场景:对象存活率较低的情况。
标记清除法:GC回收时对存活的对象进行标记,清除阶段对没有标记的对象进行清除。
优势是不需要额外的空间,劣势是两次扫描会严重浪费时间,产生内存碎片。
优化的方法是:采用压缩,防止内存碎片产生,再次扫描,向一端移动存活的对象,但是还是多了一个移动成本资源。
轻GC和重GC运行原理?
对比以上3个方法分析:
内存效率:复制算法>标记清除算法>标记压缩算法
内存整齐度:复制算法=标记压缩算法>标记清除算法
内存利用率:标记压缩算法 = 标记清除算法>复制算法

采用何种算法取决于实际场景分析:

新生代:存活率低,采用复制算法;
老年带:区域大:存活率,采用标记清除法和标记压缩算法混合使用。

推荐书籍:《深入理解JVM》
面对一个新事物的快节奏学习:是什么,干什么,怎么干,看面试题!!!

补充:
JVM只有一个主内存,每个线程都有自己的工作内存,是从主内存拷贝的;
线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值