JVM一

警醒自己不断学习和成长

1. 常见的面试题

  1. 请你谈谈你对jvm的理解?Java8虚拟机有什么更新、

    jvm是Java跨平台的基础,它由JVM运行时数据区、执行引擎、本地库接口、本地方法库组成

  2. 什么是OOM,请你说说OOM产生的原因?如何分析

    OOM:OutOfMemoryError,内存溢出,当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个ERROR。

    产生的原因:

    • 虚拟机内存不够:
      • 当前应用占用服务器的内存大小,可以尝试调大虚拟机内存测试。
    • 内存溢出/内存泄漏
      • 程序运行时可以增加参数,当出现内存溢出时生成Dump内存快照进行分析。
  3. jvm的常用调优参数有哪些

    -Xmx :设置堆的最大值

    -Xms :设置堆的最小值

    ...

  4. 内存快照抓取,如何分析抓取,命令是什么?

    使用-XX:+HeapDumpOnOutOfMemoryError命令,设置在产生OOM异常的是生成内存快照

  5. 堆里面分区:Eden,Survial(from to)、老年区

  6. GC垃圾收集算法有哪几个?谈谈利弊

  7. jvm垃圾回收的时候如何确定垃圾,GCRoots

  8. -x -xx 参数你用过哪些

  9. 你常用的项目发布后配置过jvm调优参数

  10. 引用、强引用、弱引用、虚引用都是什么,请你谈谈

  11. GC垃圾回收器和GC算法的关系?分别有哪些?

  12. G1垃圾回收器的特点

  13. OOM见过几种

java -XX:+PrintCommandLineFlags -version

C:\Users\tianxc>java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=266536512 -XX:MaxHeapSize=4264584192 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)

2.JVM的位置

3.JVM的体系结构

我们所谓的调优就是在调

4.类加载器

类的加载、连接和初始化

加载:查找并加载类的二进制数据

连接:

  • 验证:保证被加载的类的正确性
  • 准备:给类的静态变量分配内存空间,赋一个默认的初始值
  • 解析:把类中的符号引用转换为直接引用

在把Java编译为class文件的时候,虚拟机并不知道所引用的地址,助记符:符号引用!转为真正的直接引用,找到对应的直接地址。

初始化:给类的静态变量赋正确的值。

public class Test{
    public static int a=1;
}
//1.加载 编译文件为.class文件,通过类加载,加载到JVM

//2.连接
//验证(1) 保证class类文件没有问题
//准备(2) 给int类型分配内存空间,a=0;
//解析(3) 符号引用转换为直接引用。

//3.初始化
//经过这个阶段的解析,把1赋值给变量a

类的加载 static

package com.ruyidd.classloader;

/**
 * @author: tianxc
 * @date: 2020-03-10 15:13
 * @desc: JVM参数:-XX:+PrintGCDetails//打印GC垃圾回收信息
 * -XX:+TraceClassLoading 打印类加载信息
 * rt.jar   jdk出厂自带的,最高级别的类加载器要加载的。
 */
public class Demo01 {

    public static void main(String[] args) {
        System.out.println(MyChild01.str2);
    }
}
class Myparent01{

    private static String str = "hello,world";
    static {
        System.out.println("Myparent01 static");
    }

}
class MyChild01 extends Myparent01{
    public static String str2 = "hello,str2";
    static {
        System.out.println("MyChild1 static");
    }
}

final 常量在编译阶段的时候就放入了常量池:

这个代码中将常量放到了Demo02 的常量池中,之后Demo02与MyParent02就没有关系了

package com.ruyidd.classloader;

/**
 * @author: tianxc
 * @date: 2020-03-10 16:55
 * @desc:
 */
public class Demo02 {
    public static void main(String[] args) {
        //直接从常量池中获取
        System.out.println(MyParent02.str);
    }

}
class MyParent02{
    public static final String str = "hello world";
//
    static {
        System.out.println("MyParent02 static");
    }
}

package com.ruyidd.classloader;

import java.util.UUID;

/**
 * @desc: 当一个常量的值并非编译的期间可以确定的,
 * 那这个值就不会被放入方法调用类的常量池中
 * 程序运行期间的时候,会主动使用常量所在的类
 */
public class Demo03 {

    public static void main(String[] args) {
        System.out.println(MyParent03.str);
    }
}

class MyParent03{
    public static final String str = UUID.randomUUID().toString();

    static {
        System.out.println("MyParent03 static");
    }
}

ClassLoader分类

  1. Java虚拟机自带的加载器
    • BootStrap 根加载器(加载系统的包,JDK核心库中的类 rt.jar)
    • Ext 扩展类加载器(加载一些扩展jar包中的类)
    • Sys/App 系统(应用类)加载器(我们自己编写的类)
  2. 用户自定义的加载器
    • ClassLoader,只需要继承这个抽象类即可,定义自己的类加载器

package com.ruyidd.classloader;

/**
 * @author: tianxc
 * @date: 2020-03-10 17:17
 * @desc:
 */
public class Demo04 {

    public static void main(String[] args) {
        Object o = new Object();
        Demo04 demo04 = new Demo04();
//null 在这里并不代表没有,只是Java触及不到
        System.out.println(o.getClass().getClassLoader());//null
        System.out.println(demo04.getClass().getClassLoader());//sun.misc.Launcher$AppClassLoader@18b4aac2


        System.out.println(demo04.getClass().getClassLoader().getParent());//sun.misc.Launcher$ExtClassLoader@1b6d3586
        System.out.println(demo04.getClass().getClassLoader().getParent().getParent());//null


    }
}

双亲委派机制:一层一层的让父类去加载,如果顶层的加载器不能加载,然后再向下类推。

双亲委派机制可以保护Java的核心类不会被自己定义的类所替代

5.native:本地方法

native:只要是带了这个关键字,说明Java的作用范围达不到,只能去调用底层C语言的库

Robot

JNI:Java Native Integer(Java本地方法接口)

6.程序计数器

每个线程都有一个程序计数器,线程私有的。

程序计数器就是一块十分小的内存空间:(几乎可以忽略不计)

作用:当前字节码执行的行号指示器。

bipush 将int、float、String、常量值推送到栈顶;

istore 将一个数值从操作数栈存储到局部变量表

iadd

imul

7.方法区

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

JDK1.7之前

永久代:用于存储一些虚拟机加载类信息,常量,字符串,静态变量等。这些东西都会放到永久代中;永久代的大小空间是有限的:如果满了:OutOfMemoryError:PermGen

JDK1.8

(HotSpot jvm在jdk1.8的时候)彻底将永久代移除,Java Heap中或者Metaspace(Native Heap)元空间

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

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

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

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

-XX:MetaspaceSize10m

如果元空间满了:OutOfMemoryError:MetaSpace

8.栈(Stack)

栈和队列(栈和队列都是基本的数据结构。)

栈和队列都是基本的数据结构:

程序运行的过程其实就是压栈的过程。

栈空了,线程就结束了。

Stack 栈是什么

栈就是管理程序的运行

存储一些基本类型的值,对象的引用,方法等。。。

栈的优势:存取速度比堆快,仅次于寄存器,栈的数据不可以共享。

StackOverflowError 栈溢出

public class Demo01 {

    public static void main(String[] args) {
        a();
    }
//Exception in thread "main" java.lang.StackOverflowError
    private static void a(){
        a();
    }
}

栈里面是一定不会存在垃圾回收的问题的,只要线程一旦结束,该栈就结束了,生命周期和线程一致。

Stack 原理

Java栈的组成元素:栈帧

栈(存什么?)+堆+方法区的交互图

栈存:本地变量表,基本类型数据(int,double...),对象引用,方法索引...

我们的这个栈主要是HotSpot(指针)

3中JVM:

  • SUN公司 HotSpot
  • BEA公司 JRockit
  • IBM公司 J9VM

9.堆(Heap)

Java7之前:

Heap堆,一个jvm实例中只有一个堆,对的内存大小是可以调节的。

可以存的内容:类、方法、常量、保存了类型引用的真实信息。

分为三个部分:

  • 新生区:Young(Eden-s0-s1)
  • 养老区:Old Tenure
  • 永久区:Perm

堆内存在逻辑上分为三个部分:新生,养老,永久(JDK1.8以后叫元空间)

物理上只有新生,养老;元空间在本地内存中,不在JVM中。

GC垃圾回收主要是在新生区和养老区,又分为普通的GC和Full GC,如果堆满了,就会爆出OutOfMemoryError

新生区

新生区就是一个类诞生,成长消亡的地方

新生区细分:Eden,S0、S1(from、to),所有的类都在Eden被new出来的,慢慢的当Eden满了,程序还需要创建对象的时候,就会触发一次轻量级GC;清理完一次垃圾之后,会将活下来的对象,会放入幸存者区(s0,s1);清理了15次之后,出现了一些极其顽强的对象,有些对象突破了15次的垃圾回收,这时候就会将这个对象送入养老区,运行了几个月之后,养老区满了,就会触发一次Full GC;如果整个空间彻彻底底的满了,就OOM了。

Sun HotSpot虚拟机中,内存管理(分代管理机制,不同的区域使用不同的算法。)

Eden from to

养老区

15次都幸存下来的对象进入养老区,养老区满了之后,触发Full GC

默认是15次,可以修改

永久区(Perm)

放一些JDK自身携带的Class,Integer的元数据

几乎不会被垃圾回收的:

OutOfMemoryError:PermGen在项目启动的时候永久代不够用了:可能是因为加载了大量的第三方包。

jdk1.6之前:有永久代、常量池在方法区

jdk1.7:有永久代,但是开始尝试去永久代,常量池在堆中

jdk1.8:永久代没有了,取而代之的是元空间;常量池在元空间中。

方法区和堆一样,是共享的区域,是JVM规范中的一个逻辑部分,也称他为非堆

元空间:是本地内存

10.堆内存调优(初始)

调整测试一:

package com.ruyidd.heap;

/**
 * 默认情况
 * maxMemory=3791650816(字节) 3616.0MB(虚拟机试图使用的最大的内存 一般是物理内存的1/4)
 * totalMemory=257425408(字节)    245.5MB(虚拟机默认初始内存总量 一般是物理内存的1/64)
 *
 * 我们可以自定义堆内存的总量:
 * -XX:+PrintGCDetails;//输出详细的垃圾回收信息
 * -Xmx:最大分配内存 1/4
 * -Xms:初始分配的内存大小1/64
 * -Xmx1024m -Xms1024m -XX:+PrintGCDetails
 */
public class Demo01 {

    public static void main(String[] args) {
        //获取堆内存的初始大小和最大大小
        long maxMemory = Runtime.getRuntime().maxMemory();
        long totalMemory = Runtime.getRuntime().totalMemory();
        System.out.println("maxMemory="+maxMemory+"(字节)\t"+(maxMemory/1024/(double)1024)+"MB");
        System.out.println("totalMemory="+totalMemory+"(字节)\t"+(totalMemory/1024/(double)1024)+"MB");
    }
}

测试二:OOM

package com.ruyidd.heap;

import java.util.Random;

/**
 * -Xmx8m -Xms8m -XX:+PrintGCDetails
 *
 * [GC (Allocation Failure) [PSYoungGen: 1536K->488K(2048K)] 1536K->640K(7680K), 0.0009800 secs] [Times: user=0.05 sys=0.00, real=0.00 secs]
 * [Full GC (Ergonomics) [PSYoungGen: 1981K->0K(2048K)] [ParOldGen: 4775K->2733K(5632K)] 6757K->2733K(7680K), [Metaspace: 3204K->3204K(1056768K)], 0.0039491 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
 * 1. GC类型:GC——普通GC;Full GC——重GC
 * 2. 1536K 执行GC之前的大小
 * 3. 488K 执行GC之后的大小
 * 4. (7680K) young 的total大小
 * 5. 0.0009800 secs清理的时间
 * 6. user 总计GC所占用CPU的时间  sys os调用等待的时间 real 应用暂停的时间
 *
 * GC的执行方式:串行执行STW(stop The World);并行执行:G1
 */
public class Demo02 {

    public static void main(String[] args) {
        String str = "lkasjdflkajlfkjakdjfalkdsfj";
        while (true){
            str += str
                    + new Random().nextInt(999999999)
                    + new Random().nextInt(999999999);
        }
        //出现的问题 java.lang.OutOfMemoryError: Java heap space
    }
}

11.Dump内存快照

查看工具:

  1. Jconsole
  2. Eclipse(MAT)
  3. Idea(Jprofile插件)

Jprofile插件

快速体验

猜你喜欢

转载自www.cnblogs.com/tianxc/p/12459660.html