JVM

JVM探究

  • 谈谈对JVM的理解?Java8虚拟机和之前的变化更新?
  • 什么是OOM,什么是栈溢出Stack Overflow Error ?怎么分析?
  • JVM的常用调优参数有哪些?
  • 内存快照如何抓取,怎么分析Dump文件?
  • 谈谈JVM中,你对类加载器的认识?
  1. JVM的位置
    在这里插入图片描述
  2. JVM的体系结构
    在这里插入图片描述
    栈中不会有垃圾,主要在堆和方法区,主要是在堆(99%)
    在这里插入图片描述

在这里插入图片描述
3. 类加载器
作用:加载Class文件,new Student()引用在栈里,具体的对象在堆中
类是模板,对象是具体的

类在经过Class Loader之后的变化如下图:
在这里插入图片描述

1 虚拟机自带的加载器
2 启动类(根)加载器 BOOT
3 扩展类加载器 EXC
4 应用程序加载器 APP
5. 双亲委派机制
6. 沙箱安全机制

加载器

/**
 * @program: jvm
 * @description: Car
 * @author: Mr.Luo
 * @create: 2020-07-18 09:02
 */
public class Car {
    public static void main(String[] args) {
        //类是模板,对象是具体的
        Car car1 = new Car();
        Car car2 = new Car();
        Car car3 = new Car();

        System.out.println(car1.hashCode());
        System.out.println(car2.hashCode());
        System.out.println(car3.hashCode());

        System.out.println("==================");

        Class<? extends Car> aClass1 = car1.getClass();
        Class<? extends Car> aClass2 = car1.getClass();
        Class<? extends Car> aClass3 = car1.getClass();

        System.out.println(aClass1.hashCode());
        System.out.println(aClass2.hashCode());
        System.out.println(aClass3.hashCode());

    }
}

输出:
951007336
2001049719
1528902577
==================
1804094807
1804094807
1804094807

/**
 * @program: jvm
 * @description: Car
 * @author: Mr.Luo
 * @create: 2020-07-18 09:02
 */
public class Car {
    public static void main(String[] args) {
        //类是模板,对象是具体的
        Car car1 = new Car();
        Car car2 = new Car();
        Car car3 = new Car();

        System.out.println(car1.hashCode());
        System.out.println(car2.hashCode());
        System.out.println(car3.hashCode());

        System.out.println("==================");

        Class<? extends Car> aClass1 = car1.getClass();

        ClassLoader classLoader = aClass1.getClassLoader();

        System.out.println(classLoader); //AppClassLoader
        System.out.println(classLoader.getParent()); //ExtClassLoader  在\jre\lib\ext
        System.out.println(classLoader.getParent().getParent()); //null 1.不存在 2.java获取不到
    }
}

输出:
1846274136
1639705018
1627674070
==================
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@511d50c0
null



Tips:
null表示Java调用不到,底层有C、C++
比如说Thread的start()

双亲委派机制

package java.lang;

/**
 * @program: jvm2
 * @description:
 * @author: Mr.Luo
 * @create: 2020-07-18 10:22
 * 自定义的java.lang.String
 */
public class String {

    public String toString() {
        return "Hello";
    }

    public static void main(String[] args) {
        String s = new String();
        s.toString();
    }
}

报错:
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
   public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application
	原因:
	双亲委派机制:安全
		1. APP(应用加载器)---> EXC(扩展加载器)--> BOOT(最终执行)
		2. BOOT(jre/lib/rt.jar/)里面没有,去EXC(jre/lib/ext)找,然后去APP
		3. 找到BooT启动类加载器,发现有一个String类,就是用这个,而不会使用我们自己定义的,防止程序员制造一些bug

一图胜千言

在这里插入图片描述

代码如下:可以正常打印,并使用当前应用加载器

/**
 * @program: jvm2
 * @description:
 * @author: Mr.Luo
 * @create: 2020-07-18 10:30
 */
public class Student {
    @Override
    public String toString() {
        return "Hello";
    }

    public static void main(String[] args) {
        Student student = new Student();
        
        System.out.println(student.getClass().getClassLoader());

        System.out.println(student.toString());
        
    }
}

输出:
sun.misc.Launcher$AppClassLoader@18b4aac2
Hello

双亲委派机制:
1 类加载器收到类加载的请求
2 将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器
3 启动加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则,抛出异常,通知子加载器进行加载
4 重复步骤3
Class Not Found ~

沙箱安全机制

我们都知道,程序员编写一个Java程序,默认的情况下可以访问该机器的任意资源,比如读取,删除一些文件或者网络操作等。当你把程序部署到正式的服务器上,系统管理员要为服务器的安全承担责任,那么他可能不敢确定你的程序会不会访问不该访问的资源,为了消除潜在的安全隐患,他可能有两种办法:

让你的程序在一个限定权限的帐号下运行。 利用Java的沙箱机制来限定你的程序不能为非作歹。以下用于介绍该机制。

什么是沙箱?
Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。

所有的Java程序运行都可以指定沙箱,可以定制安全策略。

java中的安全模型

在Java中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱 (Sandbox) 机制。

JDK1.0安全模型如下图所示
在这里插入图片描述

但如此严格的安全机制也给程序的功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时候,就无法实现。因此在后续的 Java1.1 版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。

JDK1.1安全模型如下图所示
在这里插入图片描述

在 Java1.2 版本中,再次改进了安全机制,增加了代码签名。不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。

JDK1.2安全模型如下图所示

在这里插入图片描述
当前最新的安全机制实现,则引入了域 (Domain) 的概念。虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域 (Protected Domain),对应不一样的权限 (Permission)。存在于不同域中的类文件就具有了当前域的全部权限,

JDK1.6安全模型如下图所示
在这里插入图片描述
以上提到的都是基本的 Java 安全模型概念,在应用开发中还有一些关于安全的复杂用法,其中最常用到的 API 就是 doPrivileged。doPrivileged 方法能够使一段受信任代码获得更大的权限,甚至比调用它的应用程序还要多,可做到临时访问更多的资源。有时候这是非常必要的,可以应付一些特殊的应用场景。例如,应用程序可能无法直接访问某些系统资源,但这样的应用程序必须得到这些资源才能够完成功能。

组成沙箱的基本组件:

  • 字节码校验器(bytecode verifier):确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。
  • 类装载器(class loader):其中类装载器在3个方面对Java沙箱起作用
  1. 它防止恶意代码去干涉善意的代码;
  2. 它守护了被信任的类库边界;
  3. 它将代码归入保护域,确定了代码可以进行哪些操作。

虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成,每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。

类装载器采用的机制是双亲委派模式。

  1. 从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;
  2. 由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效。
  • 存取控制器(access controller):存取控制器可以控制核心API对操作系统的存取权限,而这个控制的策略设定,可以由用户指定。
  • 安全管理器(security manager):是核心API和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高。
  • 安全软件包(security package):java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括:

1 安全提供者
2 消息摘要
3 数字签名
4 加密
5 鉴别

参考连接:java中的安全模型(沙箱机制)

  1. Native
    native: 凡是带了native关键字的,说明java的作用范围达不到了,回去调用底层语言的库!

会进入本地方法栈,调用本地方法接口 JNI

JNI作用: 扩展Java的使用,融合不同的编程语言为Java所用!最初是C 、C++

Java诞生的时候 C C++横行,想要立足,必须要有调用C 、 C++的程序

它在内存区域中,专门开辟了一块标记区域,Native Method Stack,等级native方法

package com.kuang;

/**
 * @program: jvm2
 * @description:Native
 * @author: Mr.Luo
 * @create: 2020-07-18 12:55
 */
public class Demo {
    public static void main(String[] args) {
        new Thread().start();
    }

    //native: 凡是带了native关键字的,说明java的作用范围达不到了,回去调用底层语言的库!
    //会进入本地方法栈,调用本地方法接口 JNI
    //JNI作用: 扩展Java的使用,融合不同的编程语言为Java所用!最初是C 、C++
    //Java诞生的时候 C C++横行,想要立足,必须要有调用C 、 C++的程序
    //它在内存区域中,专门开辟了一块标记区域,Native Method Stack,等级native方法
    
    //Java驱动打印机,管理系统,掌握即可,在企业级应用中较为少见!
    private native void start0();
    
    //调用其它接口:Socket、WebService http
    

}

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

  2. 方法区
    Method Area 方法区
    方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊的方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间

静态变量、常量、类信息(构造方法,接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关

static final Class 常量池

Java内存 堆 栈 方法区如下图:
在这里插入图片描述

  1. 栈:数据结构
    程序 = 数据结构 + 算法 --> 持续学习
    程序 = 框架 + 业务逻辑 --> 吃饭

栈:先进后出 桶
队列:FIFO

为什么main方法先执行,最后结束?main最先压入栈,最后弹出栈

栈:栈内存,主管程序的运行,生命周期和线程同步;
线程结束,栈内存也就释放,对于栈来说,不存在垃圾回收问题

一旦线程结束,栈就over!
栈:8大基本类型+对象的引用+实例的方法

栈运行原理:栈帧

栈底部子帧指向上一个栈的方法 上一个栈的父帧指向栈底部方法
如下图所示:
在这里插入图片描述

栈的位置示意图如下图所示:

在这里插入图片描述
7. 三种JVM

  • Sun公司HotSpot Java Hotspot™ 64-Bit Server VM (build 25.181-b13,mixed mode)
  • BEA JRockit
  • IBM J9VM

我们学习都是: Hotspot

Heap, 一个JVM只有一个堆内存,堆内存的大小是可以调节的。

类加载器读取了类文件后,一般会把什么东西放到堆中?
类, 方法常量,变量~,保存我们所有引用类型的真实对象;

堆内存中还要细分为三个区域:

  • 新生区(伊甸园区) Young/New
  • 养老区old
  • 永久区Perm

堆内存详细划分如下图:
在这里插入图片描述
GC垃圾回收,主要是在伊甸园区和养老区~

假设内存满了,OOM,堆内存不够! java.lang.OutOfMemoryError:Java heap space

永久存储区里存放的都是Java自带的 例如lang包中的类 如果不存在这些,Java就跑不起来了

在JDK8以后,永久存储区改了个名字(元空间)

堆内存溢出如下图:
在这里插入图片描述

  1. 新生去、老年区

新生区

  • 类:诞生和成长的地方,甚至死亡;
  • 伊甸园,所有的对象都是在伊甸园区new出来的!
  • 幸存者区(0,1)

重GC和轻GC如下图:
在这里插入图片描述

  1. 永久区

这个区域常驻内存的。用来存放JDK自身携带的Class对象。Interface元数据,存储的是Java运行时的一些环境~ 这个区域不存在垃圾回收,关闭虚拟机就会释放内存

出现OOM的情况

  1. 一个启动类,加载了大量的第三方jar包。
  2. Tomcat部署了太多的应用,大量动态生成的反射类,不断地被加载,直到内存满,就会出现OOM
  • jdk1.6之前:永久代,常量池是在方法区;
  • jdk1.7:永久代,但是慢慢的退化了,去永久代,常量池在堆中
  • jdk1.8之后:无永久代,常量池在元空间

堆空间结构如下图:(圆空间逻辑上存在,物理上不存在)
在这里插入图片描述

package com.kuang;

/**
 * @program: jvm2
 * @description:
 * @author: Mr.Luo
 * @create: 2020-07-18 16:34
 */
public class Demo02 {
    public static void main(String[] args) {
        //返回虚拟机试图使用的最大内存 初始内存
        System.out.println("max: "+ (Runtime.getRuntime().maxMemory())/1024/1024);  //字节

        //返回jvm的总内存
        System.out.println("total: " + (Runtime.getRuntime().totalMemory())/1024/1024);
    }
}

默认情况下,分配到总内存是电脑内存的1/4,而初始化内存1/64

出现OOM:
1.尝试扩大堆内存,看结果
2.如果还是OOM,那就分析内存,看一下哪个地方出现了问题(专业工具)

-Xms1024m -Xmx1024m -XX:+PrintGCDetail

元空间:逻辑上存在,物理上不存在 (因为存储在本地磁盘内) 所以最后并不算在JVM虚拟机内存中

Test OOM 代码如下:
配置VM参数:
在这里插入图片描述

  1. 堆内存调优
package com.kuang;

import java.util.Random;

/**
 * @program: jvm2
 * @description: Test OOM
 * @author: Mr.Luo
 * @create: 2020-07-18 18:31
 */
public class Demo03 {
    public static void main(String[] args) {
        String str = "kuangshensayjava";
        while (true){
           str += str + new Random().nextInt(888888888) + new Random().nextInt(888888888);
        }
    }
}

输出:
/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/bin/java -Xms1m -Xms1m -XX:+PrintGCDetails "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=60049:/Applications/IntelliJ IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/lib/tools.jar:/Users/mac/IdeaProjects/jvm2/out/production/jvm2 com.kuang.Demo03
[GC (Allocation Failure) [PSYoungGen: 512K->416K(1024K)] 512K->416K(1536K), 0.0015888 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 928K->432K(1024K)] 928K->432K(1536K), 0.0007415 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 939K->512K(1024K)] 939K->520K(1536K), 0.0009282 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 1024K->501K(1536K)] 1032K->622K(2048K), 0.0015197 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1333K->490K(1536K)] 1455K->736K(2048K), 0.0015078 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1308K->938K(3072K)] 1554K->1184K(3584K), 0.0010590 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2595K->660K(3072K)] 3907K->2141K(5120K), 0.0027468 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 2708K->320K(5120K)] 816453K->814104K(826880K), 0.0032528 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1019K->416K(5120K)] 1360578K->1359975K(1403392K), 0.0073330 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 416K->192K(7680K)] 1359975K->1359959K(1405952K), 0.0070044 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 192K->0K(7680K)] [ParOldGen: 1359767K->273627K(300544K)] 1359959K->273627K(308224K), [Metaspace: 3706K->3706K(1056768K)], 0.3502884 secs] [Times: user=0.13 sys=0.36, real=0.35 secs] 
[GC (Allocation Failure) [PSYoungGen: 129K->32K(7680K)] 1365305K->1365208K(1405952K), 0.0060251 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 32K->0K(7680K)] [ParOldGen: 1365176K->273762K(317440K)] 1365208K->273762K(325120K), [Metaspace: 3706K->3706K(1056768K)], 0.1229017 secs] [Times: user=0.11 sys=0.07, real=0.12 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(11264K)] 1092424K->1092424K(1409536K), 0.0054834 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(11264K)] 1092424K->1092424K(1409536K), 0.0055316 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(11264K)] [ParOldGen: 1092424K->819401K(897536K)] 1092424K->819401K(908800K), [Metaspace: 3706K->3706K(1056768K)], 0.1197677 secs] [Times: user=0.37 sys=0.01, real=0.12 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(15872K)] 819401K->819401K(1414144K), 0.0028349 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(15872K)] [ParOldGen: 819401K->819347K(910848K)] 819401K->819347K(926720K), [Metaspace: 3706K->3706K(1056768K)], 0.1161083 secs] [Times: user=0.39 sys=0.01, real=0.12 secs] 
Heap
 PSYoungGen      total 15872K, used 451K [0x0000000795580000, 0x0000000796680000, 0x00000007c0000000)
  eden space 15360K, 2% used [0x0000000795580000,0x00000007955f0e50,0x0000000796480000)
  from space 512K, 0% used [0x0000000796600000,0x0000000796600000,0x0000000796680000)
  to   space 1024K, 0% used [0x0000000796480000,0x0000000796480000,0x0000000796580000)
 ParOldGen       total 1398272K, used 819347K [0x0000000740000000, 0x0000000795580000, 0x0000000795580000)
  object space 1398272K, 58% used [0x0000000740000000,0x0000000772024e88,0x0000000795580000)
 Metaspace       used 3737K, capacity 4536K, committed 4864K, reserved 1056768K
  class space    used 418K, capacity 428K, 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.kuang.Demo03.main(Demo03.java:15)

在一个项目中,突然出现了OOM故障,那么该如何排除 研究为什么出错~

  • 能够看到代码第几行出错:内存快照分析工具,MAT, Jprofiler
  • Dubug, 一行行分析代码!

MAT, Jprofiler作用

  • 分析Dump内存文件,快速定位内存泄露;
  • 获得堆中的数据
  • 获得大的对象~

MAT是eclipse集成使用

Jprofile使用

1.在idea中下载jprofile插件

2.联网下载jprofile客户端

3.在idea中VM参数中写参数 -Xms1m -Xmx8m -XX: +HeapDumpOnOutOfMemoryError

4.运行程序后在jprofile客户端中打开找到错误 告诉哪个位置报错
命令参数详解
// -Xms设置初始化内存分配大小/164
// -Xmx设置最大分配内存,默以1/4
// -XX: +PrintGCDetails // 打印GC垃圾回收信息
// -XX: +HeapDumpOnOutOfMemoryError //oom DUMP

  1. GC:常用算法

GC的作用区如下图:
在这里插入图片描述

JVM在进行GC时,并不是对这三个区域统一回收。 大部分时候,回收都是新生代~

  • 新生代
  • 幸存区(form,to)
  • 老年区

GC两种类:轻GC (普通的GC), 重GC (全局GC)

GC常见面试题目:

  • JVM的内存模型和分区~详细到每个区放什么?
  • 堆里面的分区有哪些? (Eden, form, to, 老年区),说说他们的特点!
  • GC的算法有哪些?怎么用的?
    标记清除法,标记整理,复制算法,引用计数器
  • 轻GC和重GC分别在什么时候发生?

算法:
引用计数法
很少使用
在这里插入图片描述
复制算法
在这里插入图片描述

  • 好处:没有内存的碎片~
  • 坏处:浪费了内存空间~ :多了一半空间永远是空to。假设对象100%存活(极端情况)
    复制算法最佳使用场景:对象存活度较低的时候;新生区~
    即在新生区使用的是复制算法

标记清除算法

扫描这些对象,对活着的对象进行标记,对没有标记的对象进行清除

在这里插入图片描述

优缺点:

优点

  • 不需要额外的空间

缺点

  • 两次扫描,严重浪费时间,会产生内存碎片

标记压缩:在标记的基础上优化

在这里插入图片描述
标记清除压缩

先标记清除几次再进行压缩

GC总结
内存效率:
复制算法>标记清除算法>标记压缩算法(时间复杂度)

内存整齐度:
复制算法=标记压缩算法>标记清除算法

内存利用率(空间):
标记压缩算法>标记清除算法>复制算法

没有最好的算法,只有最合适的算法

GC:被称为 分代收集算法
年轻代:存活率低,使用复制算法
老年代:区域大,存活率,标记清除(内存碎片不是太多) +标记压缩混合实现

  1. JMM
    1 什么是JMM?
    JMM:(Java Memory Mode)
    2 它是干嘛的?

作用:缓存一致性协议,用于定义数据读写的规则(遵守,找到这个规则)

JMM定义了线程工作内存和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存

在这里插入图片描述

由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内

存(栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有

变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变

量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自

己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回到主内存。不

能直接操作主内存里的变量,各个线程中工作内存中存储着主内存变量副本拷贝,

因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内

存来完成。

JMM关于同步的规定
1、线程解锁前,必须把共享变量的值刷新到主内存
2、线程加锁前,必须读取主内存的最新值到自己的工作内存
3、加锁解锁是同-把锁

JMM线程安全

  • 工作内存和主内存同步延迟现象导致的可见性问题

  • 可以使用synchronized 或volatile关键字解决,它们都可以使一个线程修改后的变量立即对其他线程可见。对于指令重排导致的可见性问题和有序性问题,可以利用volatile关键字解决,因为volatile的另一个作用就是禁止重排序优化。

JMM坚持三原则

可见性
各个线程对主内存中共变量的操作都是各个线程各自拷贝到自己的工作内存进行操作后再写回到主内存中的,利用volatile关键字解决可见性问题。

​ 这就可能存在一个线程AAA修改了共享变量的值但是未写回到主内存时,另外一个线程BBB又对主内存中同一个共享变量进行操作,但是此时AA线程工作内存中的共享变量对于线程BBB来说是不可见的,这种工作内存与主内存同步延迟就造成了可见性问题。

原子性
原子性是在多线程并发情况下,不会出现分割或加塞,多线程情况下会出现某个线程挂起,数据丢失,不完整,出现Atomic类包下的,保证原子性

有序性,即禁止指令重排
计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令进行重排,一般分以下三种
源代码-》编译器优化的重排-》指令并行的重排-》内存系统的重排-》最终执行的指令

单线程环境里面确保程序最终执行结果和代码顺序执行结果一致。

处理器在进行重排序时必须要考虑指令之间的数据依赖性

多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性 是无法确定的,结果无法预测
16. 总结

资料来源:百度,思维导图

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值