跟南桑深入探究JVM

1.JVM的学习方式

  • 请你谈谈你对JVM的理解?java8虚拟机和之前的变化更新?
  • 什么是OOM?什么是栈溢出?怎么分析?
  • JVM的常用调优参数有哪些?
  • 内存快照如何抓取,怎么分析Dump文件?
  • 谈谈JVM中,类加载器的认识?
  1. JVM的位置
  2. JVM的体系结构
  3. 类加载器
  4. 双亲委派机制
  5. 沙箱安全机制
  6. Native
  7. PC寄存器
  8. 方法区
  9. 三种JVM
  10. 新生区、老年区
  11. 永久区
  12. 堆内存调优
  13. GC
    1. 常用算法
  14. JMM
  15. 总结

2.JVM的体系结构

img

image-20210501103629046

image-20210508220116888

image-20210508220159430

image-20210508220337965

img

3.类加载器及双亲委派机制

作用:加载Class文件

image-20210505222132205

  1. 虚拟机自带的加载器
  2. 启动类(根)加载器(BootstrapClassLoader
  3. 扩展类加载器(ExtClassLoader
  4. 应用程序(系统类)加载器(AppClassLoader

4.Java历史-沙箱安全机制

java安全模型的核心就是java沙箱(sandbox)

什么是沙箱?

沙箱是一个限制程序运行的环境。

沙箱机制就是将java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源的访问,通过这样的措施来保证对代码的有效隔离。防止对本地系统造成破坏。沙箱主要限制系统资源访问。

那系统资源包括什么?CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也不一样。

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

在java中将执行程序分成本地代码和远程代码两种,本次代码默认视为可信任的,而远程代码则被看作是不受信受信的。对于受信的本地代码,可以访问一切本地资源。而对于非受信的远程代码在早期的java实现中,安全依赖于沙箱机制。如下图所示 JDK1.0安全模型

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

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

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

组成沙箱的基本组件

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

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

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

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

5.Native、方法区

public class Demo {
    public static void main(String[] args) {
        new Thread(() -> {

        }).start();
    }

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

PC寄存器

程序计数器:program counter register

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

方法区

Method Area 方法区

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

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

6.深入理解一下栈

栈:数据结构

栈:先进后出 、后进先出 (桶)

队列:先进先出(FIFO: first input first output)

为什么main()方法先执行,最后结束!

栈:栈内存,主管程序的运行,生命周期和线程同步

线程结束,栈内存也就是释放,对于栈来说,不存在垃圾回收问题

一旦线程结束,栈就over

栈中可以存什么:

  • 8大基本类型
    • boolean
    • byte
    • char
    • short
    • int
    • long
    • float
    • double
  • 对象引用
    • reference类型
  • 实例的方法

栈运行原理:栈帧

栈满了会抛出:StackOverflowError

栈 + 堆 + 方法区 交互关系

image-20210508173017914

JVM虚拟机栈执行原理深入详解 - 知乎 (zhihu.com)

7.走进HotSpot和堆

三种JVM

  • sun公司的 HotSpot
  • BEA JRockit
  • IBM J9 VM

我们学习的都是:HotSpot

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

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

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

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

image-20210508230542302

GC垃圾回收,主要是在伊甸园区和养老区~

加入内存满了,OOM,堆内存不够! java.lang.OutOfMemoryError:java heap space

在JDK1.8以后,永久区改了一个名字~元空间

8.新生区、永久区、堆内存调优

新生区

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

现象:经过研究,99%的对象都是临时对象!

老年区

永久区

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

一个启动类,加载了大量的第三方jar包。tomcat部署了太多的应用,大量动态生成的反射类,不断的被加载。直到内存满了,就会出现OOM

  • jdk1.6之前:永久代,常量池在方法区
  • jdk1.7:永久代,但是慢慢的退化了,去永久代,常量池在堆中
  • jdk1.8之后:无永久代,常量池在元空间

image-20210510082610574

public class JVMDemo02 {
    public static void main(String[] args) {
        //虚拟机试图使用的最大内存
        long maxMemory = Runtime.getRuntime().maxMemory();
        //虚拟机的初始化总内存
        long totalMemory = Runtime.getRuntime().totalMemory();

        System.out.println("maxMemory:" + maxMemory + "字节\t" + (maxMemory / (double) 1024 / 1024) + "MB");
        System.out.println("totalMemory:" + maxMemory + "字节\t" + (totalMemory / (double) 1024 / 1024) + "MB");
        //默认情况下,分配的最大内存是电脑内存的1/4 而初始化的内存是 1/64
        //-Xms1024m -Xmx1024m -XX:+PrintGCDetails
        /**
         * OOM:
         *   1. 尝试扩大堆内存看结果
         *   2. 分析内存,看一下哪个地方出现了问题(专业工具)
         */
        //305664K + 699392K = 1005056K = 981.5M
    }
}

输出

axMemory:1029177344字节	981.5MB
totalMemory:1029177344字节	981.5MB
Heap
 PSYoungGen      total 305664K, used 15729K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
  eden space 262144K, 6% used [0x00000000eab00000,0x00000000eba5c420,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 3187K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 343K, capacity 388K, committed 512K, reserved 1048576K

元空间:逻辑上存在,物理上不存在

idea设置JVM堆内存大小

image-20210510202938315

image-20210510203003781

如下代码设置

-Xms1m -Xmx1m -XX:+PrintGCDetails

-Xms 设置初始化内存分配大小,默认1/64

-Xmx 设置最大分配内存大小,默认1/4

-XX:+PrintGCDetails — 打印GC垃圾回收信息

-XX:+HeapDumpOnOutOfMemoryError — OOM DUMP

public class JVMDemo {
    public static void main(String[] args) {
        String str = "123334";
        while (true) {
            str = str + new Random().nextInt(999999999) + new Random().nextInt(999999999) + new Random().nextInt(999999999);
        }
    }
}

输出(部分输出,因为太大了)

[GC (Allocation Failure) [PSYoungGen: 511K->504K(1024K)] 511K->512K(1536K), 0.0009641 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1016K->504K(1024K)] 1024K->616K(1536K), 0.0006923 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
...

[GC (Allocation Failure) --[PSYoungGen: 1011K->1011K(1024K)] 1267K->1515K(1536K), 0.0005623 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
...

[Full GC (Ergonomics) [PSYoungGen: 1011K->225K(1024K)] [ParOldGen: 504K->412K(512K)] 1515K->638K(1536K), [Metaspace: 3231K->3231K(1056768K)], 0.0041239 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 730K->211K(1024K)] [ParOldGen: 412K->417K(512K)] 1143K->629K(1536K), [Metaspace: 3231K->3231K(1056768K)], 0.0039325 secs] [Times: user=0.06 sys=0.02, real=0.01 secs] 
...

[Full GC (Ergonomics) [PSYoungGen: 502K->337K(1024K)] [ParOldGen: 409K->409K(512K)] 912K->747K(1536K), [Metaspace: 3248K->3248K(1056768K)], 0.0041661 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
...

[Full GC (Ergonomics) [PSYoungGen: 503K->337K(1024K)] [ParOldGen: 409K->406K(512K)] 913K->743K(1536K), [Metaspace: 3248K->3248K(1056768K)], 0.0045866 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 503K->376K(1024K)] 909K->854K(1536K), 0.0004517 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 376K->294K(1024K)] [ParOldGen: 478K->449K(512K)] 854K->744K(1536K), [Metaspace: 3248K->3248K(1056768K)], 0.0044150 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
....

[Full GC (Ergonomics) [PSYoungGen: 427K->424K(1024K)] [ParOldGen: 434K->434K(512K)] 862K->859K(1536K), [Metaspace: 3376K->3376K(1056768K)], 0.0041869 secs] [Times: user=0.11 sys=0.00, real=0.02 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 424K->424K(1024K)] [ParOldGen: 434K->434K(512K)] 859K->858K(1536K), [Metaspace: 3376K->3376K(1056768K)], 0.0050470 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 1024K, used 490K [0x00000000ffe80000, 0x0000000100000000, 0x0000000100000000)
  eden space 512K, 95% used [0x00000000ffe80000,0x00000000ffefaa88,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 512K, used 434K [0x00000000ffe00000, 0x00000000ffe80000, 0x00000000ffe80000)
  object space 512K, 84% used [0x00000000ffe00000,0x00000000ffe6c938,0x00000000ffe80000)
 Metaspace       used 3409K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 363K, 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.zyy.jvm.JVMDemo.main(JVMDemo.java:16)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值