JVM学习笔记

本文是关于JVM的详细学习笔记,涵盖了JVM的位置、体系结构、类加载器、双亲委派机制、内存区域、垃圾收集算法等内容。探讨了JVM在Java程序中的重要性,包括如何理解和优化堆、方法区、栈和堆内存,以及如何应对内存溢出等问题。同时,文章还介绍了如何进行JVM调优和使用工具进行内存快照分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 JVN入门探究

  • 谈谈对JVM的理解?java8虚拟机和之前的变化更新?
  • OOM内存溢出,StackOverFlowError栈溢出,怎么分析?
  • JVM 的常用调优参数有哪些?
  • 内存快照如何抓取,怎么分析Dump文件?
  • 谈谈JVM中,类加载器你的认识?rt-jar ext applicatoin

2 JVM的体系研究

2.1 JVM位置

在这里插入图片描述

2.2 JVM的体系结构

在这里插入图片描述

  • 百分百没有垃圾(因为栈的运行方式,不存会在垃圾)
  • 所谓的 JVM 调优,99%都是在调方法区,其中主要的是
  • 第三方插件,主要是在执行引擎上做,类加载器上比较少

2. 类加载器

**作用:**加载Class文件

  • 类是抽象的,对象是具体的
  • 对象的引用地址在栈,具体数据在堆
  • 第三方插件,主要是在执行引擎上做,类加载器上比较少
    在这里插入图片描述

2.1 理解类加载器:

class Test{ 
public static int a = 1; 
}
//我们程序中给定的是 public static int a = 1; 

但是在加载过程中的步骤如下:

  1. 加载阶段 编译文件为 .class文件,然后通过类加载,加载到JVM
  2. 连接阶段
    第一步(验证):确保Class类文件没问题
    第二步(准备):先初始化为 a=0。(因为你int类型的初始值为0)
    第三步(解析):将引用转换为直接引用
  3. 初始化阶段: 通过此解析阶段,把1赋值为变量a
    输出都为同一个类:
public class Car {
  public static void main(String[] args) {
    Car car1 = new Car();
    Car car2 = new Car();
    Car car3 = new Car();
    Class<? extends Car> aClass = car1.getClass();
    Class<? extends Car> aClass1 = car2.getClass();
    Class<? extends Car> aClass2 = car3.getClass();
    System.out.println(aClass);
    System.out.println(aClass2);
    System.out.println(aClass2);

  }
}
Class Car

2.2 类加载器的执行顺序

类的加载指的是将类的.class文件中二进制数据读入到内存中,将其放在运行时数据区内的方法区内,然后再内存中创建一个 java.lang.Class 对象用来封装类在方法区内的数据结构。

  1. 对于静态字段来说,只有直接定义了该字段的类才会被初始化;
  2. 当一个类在初始化时,要求其父类全部都已经初始化完毕了;
  3. 所有Java虚拟机实现必须在每个类或者接口被Java程序“首次主动使用”时才初始化他们
public class Ltest {
  public static void main(String[] args) {
    System.out.println(Child.str2);
  }
}
  class MyParent{
    public  static String str1 = "hello";
    static {
      System.out.println("MyParent static");
    }
  }

  class Child extends MyParent{
    public static String str2 = "world";
    static {
      System.out.println("Child static");
    }
  }

输出:

MyParent static
Child static
world

2.3 常量池的概念

常量在编译阶段会存入到调用这个常量的方法所在的类的常量池中 本质上,调用类并没有直接用用到定义常量的类,因此并不会触发定义常量的类的初始化。 注意:这里指的是将常量存放到了MyTest2的常量池中,之后MyTest2与MyParent2就没有任何关系 了。

public class Ltest2 {
  public static void main(String[] args) {
    System.out.println(MyParent2.str3);
  }
}
class MyParent2{
  public static final String str3 = "qwe";
  static {
    System.out.println("MyParent2 static");
  }
}

qwe

当一个常量的值并非编译期间可以确定的,那么其值就不会被放到调用类的常量池中, 这是在程序运行时,会导致主动使用这个常量所在的类,显然就会导致这个类被初始化。

import java.util.UUID;

public class Ltest2 {
  public static void main(String[] args) {
    System.out.println(MyParent2.str3);
  }
}
class MyParent2{
  public static final String str3 = UUID.randomUUID().toString();
  static {
    System.out.println("MyParent2 static");
  }
}

因为这个例子的值,是只有当运行期才会被确定的值,所以需要运行static方法。而上一个例子的值,是编译时就能被确定的值。

MyParent2 static
669d1384-d4e9-4e97-bb76-7d96058b58a6

2.4 ClassLoader分类

类加载器的分类
1、Java虚拟机自带的加载器

  • 根类加载器(BootStrap)(BootClassLoader) sun.boot.class.path (加载系统的包,包含jdk核
    心库里的类)
  • 扩展类加载器(Extension)(ExtClassLoader) java.ext.dirs(加载扩展jar包中的类)
  • 系统(应用)类加载器(System)(AppClassLoader) java.class.path(加载你编写的类,编译后
    的类)
    2、用户自定义的类加载器
  • Java.long.ClassLoader的子类(继承),用户可以定制类的加载方式
    在这里插入图片描述
public class Car {
  public static void main(String[] args) {
    Car car1 = new Car();
    Class<? extends Car> aClass = car1.getClass();
    ClassLoader classLoader = aClass.getClassLoader();
    System.out.println(classLoader);//AppClassLoader 应用程序加载器  
    System.out.println(classLoader.getParent());//PlatformClassLoader 平台类加载器  \jre\lib\ext
    System.out.println(classLoader.getParent().getParent());//null   1.不存在 2.Java程序获取不到  rt.jar
  }
}

输出:

jdk.internal.loader.ClassLoaders$AppClassLoader@2f0e140b
jdk.internal.loader.ClassLoaders$PlatformClassLoader@16b98e56
null

3 双亲委派机制

  • 为了保证核心 class 安全,不被篡改
  • 防止重复加载同一个 class
  • APP --> EXC --> BOOT (最终最高级执行)
  • BOOT --> EXC --> APP (当最高级没有,再一级一级往回执行)
  • 执行顺序
    1.类加载器收到类加载的请求
    2.将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器
    3.启动加载器检查是否能够加载当前这个类载(使用findClass()方法),能加载就结束,使用当前的加载器
    4.否则,抛出异常,通知子加载器进行加载
    5.重复 3 、4 步骤

String 默认情况下是启动类加载器进行加载的,自定义一个String:

package java.lang; 
public class String { 
public static void main(String[] args) { 
              System.out.println(1); 
   } 
}

在这里插入图片描述
发现自定义的String 可以正常编译,但是永远无法被加载运行。
这是因为申请自定义String 加载时,总是启动类加载器,而不是自定义加载器,也不会是其他的加载器。
双亲委派机制可以确保Java核心类库所提供的类**,不会被自定义的类所替代**。

4 Native方法

编写多线程类启动

  public static void main(String[] args) {
    System.out.println(MyParent2.str3);
    new Thread(()->{
      
    },"123").start();
  }
}

start方法源码:

public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {

            }
        }
    }
//这个Thread是一个类,这个方法定义在这里是不是很诡异!看这个关键字native;
    private native void start0();

凡是带了native关键字的,说明 java的作用范围达不到,去调用底层C语言的库!
JNI:Java Native Interface (Java本地方法接口)
凡是带了native关键字的方法就会进入本地方法栈;
Native Method Stack 本地方法栈
本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序,Java在诞生的时候是
C/C++横行的时候,想要立足,必须有调用C、C++的程序,于是就在内存中专门开辟了一块区域处理标
记为native的代码,它的具体做法是 在 Native Method Stack 中登记native方法,在 ( Execution
Engine ) 执行引擎执行的时候加载Native Libraies。
目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或者Java系统
管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间通信很发达,比如可以使用
Socket通信,也可以使用Web Service等等,不多做介绍

4 程序计数器

程序计数器:Program Counter Register
每个线程都有一个程序计数器,是线程私有的。
程序计数器是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。是一个非常小的内存空间,几乎可以忽略不计。

5 方法区

Method Area 方法区 是 Java虚拟机规范 中定义的运行时数据区域之一,它与堆(heap)一样在线程之间共享。
Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
JDK7 之前(永久代)用于存储已被虚拟机加载的类信息、常量、字符串常量、类静态变量、即时编译器编译后的代码等数据。每当一个类初次被加载的时候,它的元数据都会被放到永久代中。永久代大小有限制,如果加载的类太多,很可能导致永久代内存溢出,即 java.lang.OutOfMemoryError: PermGen。
JDK8 彻底将永久代移除出 HotSpot JVM,将其原有的数据迁移至 Java Heap 或 Native Heap(Metaspace),取代它的是另一个内存区域被称为元空间(Metaspace)。
元空间(Metaspace):元空间是方法区的在 HotSpot JVM 中的实现,方法区主要用于存储类信息、常量池、方法数据、方法代码、符号引用等。元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
可以通过 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 配置内存大小。
如果Metaspace的空间占用达到了设定的最大值,那么就会触发GC来收集死亡对象和类的加载器。

6 栈(Stack)

栈:后进先出 / 先进后出
队列:先进先出(FIFO : First Input First Output)
在这里插入图片描述
栈管理程序运行
存储一些基本类型的值、对象的引用、方法等。
栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。
思考为什么main方法最后执行!为什么一个test() 方法执行完了,才会继续走main方法!
在这里插入图片描述
栈是每个线程独有的,每次调用函数时,栈就向下增长,调用完就弹出栈,main函数的栈没法和其他函数的栈重叠,因为当main函数出栈时,主线程就结束了。即使还有其他线程在运行,其他线程拥有自己的堆栈,相互也不会影响的。
说明:
1、栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程
结束栈内存也就释放。
2、对于栈来说不存在垃圾回收问题,只要线程一旦结束,该栈就Over,生命周期和线程一致,是线程
私有的。
3、方法自己调自己就会导致栈溢出(递归死循环测试)

public class StackDemo {
  public static void main(String[] args) {
    a();
  }
  private static void a() {
    b();
  }
  private static void b() {
    a();
  }
}

6.1 栈运行原理

Java栈的组成元素—栈帧
栈帧是一种用于帮助虚拟机执行方法调用与方法执行的数据结构。他是独立于线程的,一个线程有自己
的一个栈帧。封装了方法的局部变量表、动态链接信息、方法的返回地址以及操作数栈等信息。
第一个方法从调用开始到执行完成,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生了栈帧
F2也被压入栈中,B方法又调用了C方法,于是产生栈帧F3也被压入栈中…执行完毕后,先弹出F3,
然后弹出F2,在弹出F1…
遵循 “先进后出” / “后进先出” 的原则。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7 堆(Heap)

Java7之前
Heap 堆,一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的,类加载器读取了类文件后,需
要把类,方法,常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行,堆内存分为
三部分:

  • 新生区 Young Generation Space Young/New
  • 养老区 Tenure generation space Old/Tenure
  • 永久区 Permanent Space Perm

堆内存逻辑上分为三部分:新生,养老,永久(元空间 : JDK8 以后名称)
在这里插入图片描述
GC垃圾回收主要是在 新生区和养老区,又分为 轻GC 和 重GC,如果内存不够,或者存在死循环,就会导致 java.lang.OutOfMemoryError: Java heap space

7.1 新生区

新生区是类诞生,成长,消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。
新生区又分为两部分:伊甸区(Eden Space)和幸存者区(Survivor Space),所有的类都是在伊甸区
被new出来的,幸存区有两个:0区 和 1区,当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾
回收器将对伊甸园区进行垃圾回收(Minor GC)。将伊甸园中的剩余对象移动到幸存0区,若幸存0区也
满了,再对该区进行垃圾回收,然后移动到1区,那如果1区也满了呢?(这里幸存0区和1区是一个互相
交替的过程)再移动到养老区,若养老区也满了,那么这个时候将产生MajorGC(Full GC),进行养老
区的内存清理,若养老区执行了Full GC后发现依然无法进行对象的保存,就会产生OOM异常
“OutOfMemoryError ”。
如果出现 java.lang.OutOfMemoryError:java heap space异常,说明Java虚拟机的堆内存不够,原因
如下:
1、Java虚拟机的堆内存设置不够,可以通过参数 -Xms(初始值大小),-Xmx(最大大小)来调整。
2、代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)或者死循环

7.2 Sun HotSpot内存管理

分代管理,不同的区域使用不同的算法:
在这里插入图片描述真相:经过研究,不同对象的生命周期不同,在Java中98%的对象都是临时对象。

7.3 永久区(Perm)

永久存储区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存
储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释
放此区域所占用的内存。
如果出现 java.lang.OutOfMemoryError:PermGen space,说明是 Java虚拟机对永久代Perm内存设
置不够。一般出现这种情况,都是程序启动需要加载大量的第三方jar包,例如:在一个Tomcat下部署
了太多的应用。或者大量动态反射生成的类不断被加载,最终导致Perm区被占满。
注意:
Jdk1.6之前: 有永久代,常量池1.6在方法区
Jdk1.7: 有永久代,但是已经逐步 “去永久代”,常量池1.7在堆
Jdk1.8及之后:无永久代,常量池1.8在元空间
熟悉三区结构后方可学习JVM垃圾回收机制
实际而言,方法区(Method Area)和堆一样,是各个线程共享的内存区域,它用于存储虚拟机加载
的:类信息+普通常量+静态常量+编译器编译后的代码,虽然JVM规范将方法区描述为堆的一个逻辑部
分,但它却还有一个别名,叫做Non-Heap(非堆),目的就是要和堆分开。

对于HotSpot虚拟机,很多开发者习惯将方法区称之为 “永久代(Parmanent Gen)”,但严格本质上说
两者不同,或者说使用永久代实现方法区而已,永久代是方法区(相当于是一个接口interface)的一个
实现,Jdk1.7的版本中,已经将原本放在永久代的字符串常量池移走。
常量池(Constant Pool)是方法区的一部分,Class文件除了有类的版本,字段,方法,接口描述信息
外,还有一项信息就是常量池,这部分内容将在类加载后进入方法区的运行时常量池中存放!
在这里插入图片描述

7.3 堆内存调优

了解完基本的堆的信息之后,我们就可以简单学习下关于堆内存调优的说明了!我们是基于 HotSpot 虚
拟机的,JDK1.8;
JDK1.7:
在这里插入图片描述
JDK 1.8:
在这里插入图片描述
使用 IDEA 调整堆内存大小测试
堆内存调优
-Xms :设置初始分配大小,默认为物理内存的 “1/64”
-Xmx :最大分配内存,默认为物理内存的 “1/4”
-XX:+PrintGCDetails :输出详细的GC处理日志
代码测试:

public class Demo01 {
  public static void main(String[] args) {
    //返回Java虚拟机试图使用的最大内存量
    long maxMemory = Runtime.getRuntime().maxMemory();
    //返回Java虚拟机中的内存总量
    long totalMemory = Runtime.getRuntime().totalMemory();
    System.out.println("MAX_MEMORY"+maxMemory+"(字节)、"+(maxMemory/(double)1024/1024)+"MB");
    System.out.println("MAX_MEMORY"+totalMemory+"(字节)、"+(totalMemory/(double)1024/1024)+"MB");
  }
}
MAX_MEMORY4263510016(字节)4066.0MB
MAX_MEMORY268435456(字节)256.0MB

在IDEA中进行JVM调优参数设置,然后启动
在这里插入图片描述
发现,默认的情况下分配的内存是总内存的 1/4,而初始化的内存为 1/64 !

-Xms1024m -Xmx1024m -XX:+PrintGCDetails

VM参数调优:把初始内存,和总内存都调为 1024M,运行,查看结果!

[0.005s][warning][gc] -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead.
[0.017s][info   ][gc] Using G1
[0.026s][info   ][gc,init] Version: 15.0.1+9-18 (release)
[0.026s][info   ][gc,init] CPUs: 12 total, 12 available
[0.026s][info   ][gc,init] Memory: 16257M
[0.026s][info   ][gc,init] Large Page Support: Disabled
[0.026s][info   ][gc,init] NUMA Support: Disabled
[0.026s][info   ][gc,init] Compressed Oops: Enabled (32-bit)
[0.026s][info   ][gc,init] Heap Region Size: 1M
[0.026s][info   ][gc,init] Heap Min Capacity: 1G
[0.026s][info   ][gc,init] Heap Initial Capacity: 1G
[0.026s][info   ][gc,init] Heap Max Capacity: 1G
[0.026s][info   ][gc,init] Pre-touch: Disabled
[0.026s][info   ][gc,init] Parallel Workers: 10
[0.026s][info   ][gc,init] Concurrent Workers: 3
[0.026s][info   ][gc,init] Concurrent Refinement Workers: 10
[0.026s][info   ][gc,init] Periodic GC: Disabled
[0.027s][info   ][gc,metaspace] CDS archive(s) mapped at: [0x0000000800000000-0x0000000800b50000-0x0000000800b50000), size 11862016, SharedBaseAddress: 0x0000000800000000, ArchiveRelocationMode: 0.
[0.027s][info   ][gc,metaspace] Compressed class space mapped at: 0x0000000800b50000-0x0000000840b50000, size: 1073741824
[0.027s][info   ][gc,metaspace] Narrow klass base: 0x0000000800000000, Narrow klass shift: 3, Narrow klass range: 0x100000000
MAX_MEMORY1073741824(字节)1024.0MB
MAX_MEMORY1073741824(字节)1024.0MB
[0.154s][info   ][gc,heap,exit] Heap
[0.154s][info   ][gc,heap,exit]  garbage-first heap   total 1048576K, used 2048K [0x00000000c0000000, 0x0000000100000000)
[0.154s][info   ][gc,heap,exit]   region size 1024K, 3 young (3072K), 0 survivors (0K)
[0.154s][info   ][gc,heap,exit]  Metaspace       used 1020K, capacity 4550K, committed 4864K, reserved 1056768K
[0.154s][info   ][gc,heap,exit]   class space    used 96K, capacity 405K, committed 512K, reserved 1048576K

Process finished with exit code 0

再次证明:元空间并不在虚拟机中,而是使用本地内存。

7.4 Dump内存快照

1、IDEA插件安装
在这里插入图片描述
2、安装JProfile监控软件
下载地址:https://www.ej-technologies.com/download/jprofiler/version_92
3、安装:安装路径,建议选择一个文件名中没有中文,没有空格的路径 ,否则识别不了。然后一直点Next
4、注册码:
L-Larry_Lau@163.com#23874-hrwpdp1sh1wrn#0620
L-Larry_Lau@163.com#36573-fdkscp15axjj6#25257
L-Larry_Lau@163.com#5481-ucjn4a16rvd98#6038
L-Larry_Lau@163.com#99016-hli5ay1ylizjj#27215
L-Larry_Lau@163.com#40775-3wle0g1uin5c1#0674
5、配置IDEA运行环境
Settings–Tools–JProflier–JProflier executable选择JProfile安装可执行文件。(如果系统只装了一个版本,
启动IDEA时会默认选择)保存
6、选择你要分析的项目,点击JProfiler图标启动, 启动完成会自动弹出JProfiler窗口,在里面就可以监
控自己的代码性能了。
在这里插入图片描述
代码测试:

import java.util.ArrayList;
public class Demo03 {
  byte[] byteArray = new byte[1*1024*1024];//1M=1024K
  public static void main(String[] args) {
    ArrayList<Demo03> list = new ArrayList<>();
    int count = 0;
    try {
      while (true){
        list.add(new Demo03());
        count+=1;
      }
    } catch (Error e) {
      System.out.println("count:" + count);
      e.printStackTrace();
    }
  }
}

VM参数: -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid21432.hprof ...
Heap dump file created [6424272 bytes in 0.010 secs]
count:3
java.lang.OutOfMemoryError: Java heap space
	at Demo03.<init>(Demo03.java:3)
	at Demo03.main(Demo03.java:9)

Process finished with exit code 0

找到java_pid21432.hprof的位置,并直接打开,默认使用JProfiler进行打开。
在这里插入图片描述在这里插入图片描述

8 GC详解

在这里插入图片描述
次数频繁Young区,次数较少Old区,基本不动Perm(永久区)区
在这里插入图片描述
JVM 在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代
因此GC按照回收的区域又分了两种类型,一种是普通的GC(minor GC),一种是全局GC (major GC
or Full GC)
普通GC:只针对新生代区域的GC
全局GC:针对老年代的GC,偶尔伴随对新生代的GC以及对永久代的GC
GC面试:
1、JVM内存模型以及分区,需要详细到每个区放什么
2、堆里面的分区:Eden,Survival from to,老年代,各自的特点。
3、GC的三种收集方法:标记清除,标记整理,复制算法的原理与特点,分别用在什么地方?
4、Minor GC 与 Full GC 分别在什么时候发生?

8.1 GC四大算法

8.1.1 引用计数法

在这里插入图片描述
每个对象有一个引用计数器,当对象被引用一次则计数器加1,当对象引用失效一次,则计数器减1,对
于计数器为0的对象意味着是垃圾对象,可以被GC回收。
目前虚拟机基本都是采用可达性算法,从GC Roots 作为起点开始搜索,那么整个连通图中的对象边都是
活对象,对于GC Roots 无法到达的对象变成了垃圾回收对象,随时可被GC回收。

8.1.2 复制算法

年轻代中使用的是Minor GC,采用的就是复制算法(Copying)

在这里插入图片描述
Minor GC 会把Eden中的所有活的对象都移到Survivor区域中,如果Survivor区中放不下,那么剩下的
活的对象就被移动到Old generation中,也就是说,一旦收集后,Eden就是变成空的了
当对象在Eden(包括一个Survivor区域,这里假设是From区域)出生后,在经过一次Minor GC后,如
果对象还存活,并且能够被另外一块Survivor区域所容纳 (上面已经假设为from区域,这里应为to区
域,即to区域有足够的内存空间来存储Eden 和 From 区域中存活的对象),则使用复制算法将这些仍然
还活着的对象复制到另外一块Survivor区域(即 to 区域)中,然后清理所使用过的Eden 以及Survivor
区域(即form区域),并且将这些对象的年龄设置为1,以后对象在Survivor区,每熬过一次Minor

-XX:MaxTenuringThreshold 任期门槛=>设置对象在新生代中存活的次数

面试题:如何判断哪个是to区呢?一句话:谁空谁是to
原理解释:
年轻代中的GC,主要是复制算法(Copying)
HotSpot JVM 把年轻代分为了三部分:一个 Eden 区 和 2 个Survivor区(from区 和 to区)。默认比例
为 8:1:1,一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第
一次Minor GC后,如果仍然存活,将会被移到Survivor区,对象在Survivor中每熬过一次Minor GC ,
年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中,因为年轻代中的对象基本上
都是朝生夕死,所以在年轻代的垃圾回收算法使用的是复制算法!复制算法的思想就是将内存分为两
块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产
生内存碎片!
在这里插入图片描述
在GC开始的时候,对象只会在Eden区和名为 “From” 的Survivor区,Survivor区“TO” 是空的,紧接着进
行GC,Eden区中所有存活的对象都会被复制到 “To” , 而在 “From” 区中,仍存活的对象会更具他们的
年龄值来决定去向。年龄达到一定值 的对象会被移动到老年代中,没有达到阈值的对象会被复制到 “To
区域”,经过这次GC后,Eden区和From区已经被清空,这个时候, “From” 和 “To” 会交换他们的角色,
也就是新的 “To“ 就是GC前的”From“ , 新的 ”From“ 就是上次GC前的 ”To“。不管怎样,都会保证名为
To 的Survicor区域是空的。 Minor GC会一直重复这样的过程。直到 To 区 被填满 , ”To “ 区被填满之
后,会将所有的对象移动到老年代中。
在这里插入图片描述
因为Eden区对象一般存活率较低,一般的,使用两块10%的内存作为空闲和活动区域,而另外80%的内
存,则是用来给新建对象分配内存的。一旦发生GC,将10%的from活动区间与另外80%中存活的Eden
对象转移到10%的to空闲区域,接下来,将之前的90%的内存,全部释放,以此类推;
好处:没有内存碎片,坏处:浪费内存空间
劣势:
复制算法它的缺点也是相当明显的。
1、他浪费了一半的内存,这太要命了
2、如果对象的存活率很高,我们可以极端一点,假设是100%存活,那么我们需要将所有对象都复制一
遍,并将所有引用地址重置一遍。复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变
的不可忽视,所以从以上描述不难看出。复制算法要想使用,最起码对象的存活率要非常低才行,而且
最重要的是,我们必须要克服50%的内存浪费。

沙箱安全机制

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

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

  • java中的安全模型
    在Java中将执行程序分成本地代码远程代码两种,**本地代码默认视为可信任的,而远程代码则被看作是不受信的。**对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱 (Sandbox) 机制。
  • JDK1.0安全模型
    但如此严格的安全机制也给程序的功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时候,就无法实现

8.1.3 标记清除

说明:老年代一般是由标记清除或者是标记清除与标记整理的混合实现
什么是标记清除?
回收时,对需要存活的对象进行标记;
回收不是绿色的对象
在这里插入图片描述
当堆中的有效内存空间被耗尽的时候,就会停止整个程序(也被称为stop the world),然后进行两项
工作,第一项则是标记,第二项则是清除。
标记:从引用根节点开始标记所有被引用的对象,标记的过程其实就是遍历所有的GC Roots ,然后将所
有GC Roots 可达的对象,标记为存活的对象。
清除: 遍历整个堆,把未标记的对象清除。
缺点:这个算法需要暂停整个应用,会产生内存碎片。
用通俗的话解释一下 标记/清除算法,就是当程序运行期间,若可以使用的内存被耗尽的时候,GC线程
就会被触发并将程序暂停,随后将依旧存活的对象标记一遍,最终再将堆中所有没被标记的对象全部清
除掉,接下来便让程序恢复运行。
劣势:

  1. 首先、它的缺点就是效率比较低(递归与全堆对象遍历),而且在进行GC的时候,需要停止应用
    程序,这会导致用户体验非常差劲
  2. 其次、主要的缺点则是这种方式清理出来的空闲内存是不连续的,这点不难理解,我们的死亡对象
    都是随机的出现在内存的各个角落,现在把他们清除之后,内存的布局自然乱七八糟,而为了应付
    这一点,JVM就不得不维持一个内存空间的空闲列表,这又是一种开销。而且在分配数组对象的时
    候,寻找连续的内存空间会不太好找。

8.1.4 标记压缩

标记整理说明:老年代一般是由标记清除或者是标记清除与标记整理的混合实现。
什么是标记压缩?
在这里插入图片描述
在整理压缩阶段,不再对标记的对象作回收,而是通过所有存活对象都像一端移动,然后直接清除边界
以外的内存。可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被
清理掉,如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可,这比
维护一个空闲列表显然少了许多开销。
标记、整理算法不仅可以弥补 标记、清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内
存减半的高额代价;

8.1.5 标记清除压缩

在这里插入图片描述
小总结
内存效率:复制算法 > 标记清除算法 > 标记整理算法 (时间复杂度)
内存整齐度:复制算法 = 标记整理算法 > 标记清除算法
内存利用率:标记整理算法 = 标记清除算法 > 复制算法
可以看出,效率上来说,复制算法是当之无愧的老大,但是却浪费了太多内存,而为了尽量兼顾上面所
提到的三个指标,标记整理算法相对来说更平滑一些 , 但是效率上依然不尽如人意,它比复制算法多了
一个标记的阶段,又比标记清除多了一个整理内存的过程。
难道就没有一种最优算法吗?猜猜看,下面还有
答案 : 无,没有最好的算法,只有最合适的算法 。 -----------------> 分代收集算法
年轻代:(Young Gen)
年轻代特点是区域相对老年代较小,对象存活低。
这种情况复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对象大小有关,因而很适
用于年轻代的回收。而复制算法内存利用率不高的问题,通过hotspot中的两个survivor的设计得到缓
解。
老年代:(Tenure Gen)
老年代的特点是区域较大,对象存活率高!
这种情况,存在大量存活率高的对象,复制算法明显变得不合适。一般是由标记清除或者是标记清除与
标记整理的混合实现。Mark阶段的开销与存活对象的数量成正比,这点来说,对于老年代,标记清除或
者标记整理有一些不符,但可以通过多核多线程利用,对并发,并行的形式提标记效率。Sweep阶段的
开销与所管理里区域的大小相关,但Sweep “就地处决” 的 特点,回收的过程没有对象的移动。使其相对
其他有对象移动步骤的回收算法,仍然是是效率最好的,但是需要解决内存碎片的问题。

为什么要学JVM1、一切JAVA代码都运行在JVM之上,只有深入理解虚拟机才能写出更强大的代码,解决更深层次的问题。2、JVM是迈向高级工程师、架构师的必备技能,也是高薪、高职位的不二选择。3、同时,JVM又是各大软件公司笔试、面试的重中之重,据统计,头部的30家互利网公司,均将JVM作为笔试面试的内容之一。4、JVM内容庞大、并且复杂难学,通过视频学习是最快速的学习手段。课程介绍本课程包含11个大章节,总计102课时,无论是笔试、面试,还是日常工作,可以让您游刃有余。第1章 基础入门,从JVM是什么开始讲起,理解JDK、JRE、JVM的关系,java的编译流程和执行流程,让您轻松入门。第2章 字节码文件,深入剖析字节码文件的全部组成结构,以及javap和jbe可视化反解析工具的使用。第3章 类的加载、解释、编译,本章节带你深入理解类加载器的分类、范围、双亲委托策略,自己手写类加载器,理解字节码解释器、即时编译器、混合模式、热点代码检测、分层编译等核心知识。第4章 内存模型,本章节涵盖JVM内存模型的全部内容,程序计数器、虚拟机栈、本地方法栈、方法区、永久代、元空间等全部内容。第5章 对象模型,本章节带你深入理解对象的创建过程、内存分配的方法、让你不再稀里糊涂。第6章 GC基础,本章节是垃圾回收的入门章节,带你了解GC回收的标准是什么,什么是可达性分析、安全点、安全区,四种引用类型的使用和区别等等。第7章 GC算法与收集器,本章节是垃圾回收的重点,掌握各种垃圾回收算法,分代收集策略,7种垃圾回收器的原理和使用,垃圾回收器的组合及分代收集等。第8章 GC日志详解,各种垃圾回收器的日志都是不同的,怎么样读懂各种垃圾回收日志就是本章节的内容。第9章 性能监控与故障排除,本章节实战学习jcmd、jmx、jconsul、jvisualvm、JMC、jps、jstatd、jmap、jstack、jinfo、jprofile、jhat总计12种性能监控和故障排查工具的使用。第10章 阿里巴巴Arthas在线诊断工具,这是一个特别小惊喜,教您怎样使用当前最火热的arthas调优工具,在线诊断各种JVM问题。第11章 故障排除,本章会使用实际案例讲解单点故障、高并发和垃圾回收导致的CPU过高的问题,怎样排查和解决它们。课程资料课程附带配套项目源码2个159页高清PDF理论篇课件1份89页高清PDF实战篇课件1份Unsafe源码PDF课件1份class_stats字段说明PDF文件1份jcmd Thread.print解析说明文件1份JProfiler内存工具说明文件1份字节码可视化解析工具1份GC日志可视化工具1份命令行工具cmder 1份学习方法理论篇部分推荐每天学习2课时,可以在公交地铁上用手机进行学习。实战篇部分推荐对照视频,使用配套源码,一边练习一遍学习。课程内容较多,不要一次性学太多,而是要循序渐进,坚持学习。      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值