jvm整理


jvm从入门到精通路径

设置jvm内存大小

原文路径

在linux上

1.适用于在项目部署后,在启动的时候,采用脚本或者命令行运行的时候设置。

$ java -jar -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xms1024m -Xmx1024m -Xmn256m -Xss256k -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC newframe-1.0.0.jar

2.参数说明

-XX:MetaspaceSize=128m (元空间默认大小)
-XX:MaxMetaspaceSize=128m (元空间最大大小)
-Xms1024m (堆最大大小)
-Xmx1024m (堆默认大小)
-Xmn256m (新生代大小)
-Xss256k (棧最大深度大小)
-XX:SurvivorRatio=8 (新生代分区比例 8:2-XX:+UseConcMarkSweepGC (指定使用的垃圾收集器,这里使用CMS收集器)
-XX:+PrintGCDetails (打印详细的GC日志)

在本地idea里面改

只需要将参数值设置到VM options中即可
在这里插入图片描述

内存与垃圾回收

jvm与java体系结构

  • JVM的整体结构
    在这里插入图片描述
  • Java代码执行流程
    在这里插入图片描述

类加载子系统

总结

  • 广义上就叫类加载器,作用是把编译后的字节码文件存到方法区。
  • 双亲委派机制及优势。
  • 判断两个class对象。
  • 类加载过程:类的加载-> 验证 -> 准备 -> 解析 -> 初始化。

类加载子系统作用

在这里插入图片描述

  • 类加载器子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识。
  • ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。
  • 加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)。
  • 类加载器ClasLoader角色。ClassLoader类是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器)。
    在这里插入图片描述
    1、class file存在于本地硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到JVM当中来根据这个文件实例化出n个一模一样的实例。
    2、class file加载到JVM中,被称为DNA元数据模板,放在方法区。
    3、在.class文件->JVM->最终成为元数据模板,此过程就要一个运输工具(类装载器Class Loader),扮演一个快递员的角色。

双亲委派机制

  • Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式。
  • 工作原理
    ● 1)如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
    ● 2)如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;
    ● 3)如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
    在这里插入图片描述
  • 好处:
    ● 避免类的重复加载
    ● 保护程序安全,防止核心API被随意篡改

其它

  • 如何判断两个class对象是否相同
    ● 类的完整类名必须一致,包括包名。
    ● 加载这个类的ClassLoader(指ClassLoader实例对象)必须相同。
    ● 换句话说,在JVM中,即使这两个类对象(class对象)来源同一个Class文件,被同一个虚拟机所加载,但只要加载它们的ClassLoader实例对象不同,那么这两个类对象也是不相等的。

执行引擎

概述

  • 翻译员,将字节码指令编译为本地机器上的指令。
  • 各种用二进制编码方式表示的指令,叫做机器指令码。开始,人们就用它采编写程序,这就是机器语言机器语言虽然能够被计算机理解和接受,但和人们的语言差别太大,不易被人们理解和记忆,并且用它编程容易出差错。用它编写的程序一经输入计算机,CPU直接读取运行,因此和其他语言编的程序相比,执行速度最快。机器指令与CPU紧密相关,所以不同种类的CPU所对应的机器指令也就不同。
  • 由于计算机只认识指令码,所以用汇编语言编写的程序还必须翻译成机器指令码,计算机才能识别和执行。

本地方法接口

  • 让虚拟机内部运行的代码能够与用其它编程语言(如 C、C++ 和汇编语言)编写的应用程序和库进行交互。这也是java在性能、和底层打交道方面的缺点。

概述

  • 多线程在一个特定的时间段内只会执行其中某一个线程的方法,CPU会不停地做任务切换,这样必然导致经常中断或恢复,如何保证分毫无差呢?为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。

运行时数据区

运行时数据区

  • 类加载完成后的阶段。
  • 类加载完成后执行引擎对类进行使用,同时执行引擎将会使用到我们运行时数据区。
    在这里插入图片描述

程序计数器(PC寄存器)

总结

  • 记录线程所执行的字节码时执行到哪。

虚拟机栈

总结

  • 栈解决如何处理数据,保存方法的局部变量,并参与方法的调用和返回。堆解决数据怎么放,放哪里。
  • 可能出现的异常。
  • 内部结构。

概述

  • 栈是运行时的单位,而堆是存储的单位。
  • 栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放,放哪里。
  • Java虚拟机栈早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的Java方法调用,是线程私有的。主管Java程序的运行,它保存方法的局部变量、部分结果,并参与方法的调用和返回。

栈中可能出现的异常

  • java 虚拟机规范允许Java栈的大小是动态的或者是固定不变的。
    ● 如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个StackOverflowError 异常。(比如程序不断的进行递归调用,而且没有退出条件)
    ● 如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个 OutOfMemoryError 异常。

栈的存储单位

  • 每个线程都有自己的栈,栈中的数据都是以栈帧(Stack Frame)的格式存在。
  • 栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。
  • 栈帧的内部结构:
    ● 局部变量表(Local Variables)
    ● 操作数栈(operand Stack)(或表达式栈)
    ● 动态链接(DynamicLinking)(或指向运行时常量池的方法引用)
    ● 方法返回地址(Return Address)(或方法正常退出或者异常退出的定义

局部变量表

● 定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用(reference),以及returnAddress类型。
● 由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题
● 局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。
● 方法嵌套调用的次数由栈的大小决定。一般来说,栈越大,方法嵌套调用次数越多。对一个函数而言,它的参数和局部变量越多,使得局部变量表膨胀,它的栈帧就越大,以满足方法调用所需传递的信息增大的需求。进而函数调用就会占用更多的栈空间,导致其嵌套调用次数就会减少。
● 局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁。

操作数栈

  • 每一个独立的栈帧除了包含局部变量表以外,还包含一个后进先出(Last-In-First-Out)的 操作数栈,也可以称之为表达式栈(Expression Stack)。
  • 操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)和 出栈(pop)。主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。

动态链接(Dynamic Linking)

  • 如果被调用的方法在编译期无法被确定下来,只能够在程序运行期将调用的方法的符号转换为直接引用,由于这种引用转换过程具备动态性,因此也被称之为动态链接。
  • 在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用(Symbolic Reference)保存在class文件的常量池里。比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
  • 常量池的作用:就是为了提供一些符号和常量,便于指令的识别。

方法返回地址(return address)

  • 存放调用该方法的pc寄存器的值。一个方法的结束,有两种方式:正常执行完成、出现未处理的异常,非正常退出。
  • 正常完成出口和异常完成出口的区别在于:通过异常完成出口退出的不会给他的上层调用者产生任何的返回值。

本地方法栈

  • 管理本地方法的调用。

堆(Heap)

总结

  • 虚拟机中内存最大的一块,用来存放对象实例。
  • 细分:新生代、老年代、元空间。
  • 新生代与老年代。

概述

  • 一个进程只有一个JVM,但是进程包含多个线程,他们是共享同一堆空间。
  • 在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。
  • 细分:新生代、老年代、元空间。

设置堆内存大小

  • “-Xms"用于表示堆区的起始内存,等价于-XX:InitialHeapSize
  • “-Xmx"则用于表示堆区的最大内存,等价于-XX:MaxHeapSize
  • 通常会将-Xms和-Xmx两个参数配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能。
  • 默认情况下 初始内存大小:物理电脑内存大小 / 64 最大内存大小:物理电脑内存大小 / 4

年轻代与老年代

  • 存储在JVM中的Java对象可以被划分为两类:一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速;另外一类对象的生命周期却非常长,在某些极端的情况下还能够与JVM的生命周期保持一致。
  • 配置新生代与老年代在堆结构的占比:
    ● 默认-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3
    ● 可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5

元空间

  • JDK8 使用元空间取代永久代,方法区存放在元空间中。
  • JDK8 使用元空间取代永久代,HotSpot 提供了一些参数作为元空间防御措施,例如 -XX:MetaspaceSize 指定元空间初始大小,达到该值会触发 GC 进行类型卸载,同时收集器会对该值进行调整,如果释放大量空间就适当降低该值,如果释放很少空间就适当提高。
  • XX:MetaspaceSize:设置初始的元空间大小。对于一个64位的服务器端JVM来说,其默认的-XX:MetaspaceSize值为21MB。这就是初始的高水位线,一旦触及这个水位线,Full GC将会被触发并卸载没用的类(即这些类对应的类加载器不再存活),然后这个高水位线将会重置。新的高水位线的值取决于GC后释放了多少元空间。如果释放的空间不足,那么在不超过MaxMetaspaceSize时,适当提高该值。如果释放空间过多,则适当降低该值。
  • 如果初始化的高水位线设置过低,上述高水位线调整情况会发生很多次。通过垃圾回收器的日志可以观察到Full GC多次调用。为了避免频繁地GC,建议将-XX:MetaspaceSize设置为一个相对较高的值。
  • 元数据区大小可以使用参数 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize指定。

图解对象分配过程

  • 设置去养老区的阈值:-Xx:MaxTenuringThreshold= N,默认是15次。多次没有被回收就会去养老区。
    在这里插入图片描述

内存分配策略

  • 如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到survivor空间中,并将对象年龄设为1。对象在survivor区中每熬过一次MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁,其实每个JVM、每个GC都有所不同)时,就会被晋升到老年代。
  • 对象晋升老年代的年龄阀值,可以通过选项-XX:MaxTenuringThreshold来设置。
    在这里插入图片描述
  • 针对不同年龄段的对象分配原则如下所示:
    ● 优先分配到Eden
    ● 大对象直接分配到老年代(尽量避免程序中出现过多的大对象)
    ● 长期存活的对象分配到老年代
    ● 动态对象年龄判断:如果survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
    ● 空间分配担保: -XX:HandlePromotionFailure

为对象分配内存:TLAB

  • 从内存模型而不是垃圾收集的角度,对Eden区域继续进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内。
  • 多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能够提升内存分配的吞吐量,因此我们可以将这种内存分配方式称之为快速分配策略。
  • 据我所知所有OpenJDK衍生出来的JVM都提供了TLAB的设计。
  • 堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据。由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的。 为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度。

方法区

栈、堆、方法区的交互关系

在这里插入图片描述

理解

  • 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误:java.lang.OutOfMemoryError: PermGen space 或者java.lang.OutOfMemoryError: Metaspace(加载大量的第三方的jar包;Tomcat部署的工程过多(30~50个);大量使用反射、动态代理、CGLib等字节码框架) 。
  • 方法区的大小不必是固定的,JVM可以根据应用的需要动态调整。

方法区内部结构

  • 存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。
    在这里插入图片描述
内部结构

在这里插入图片描述

  • 类型信息
    对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必须在方法区中存储以下类型信息:
  1. 这个类型的完整有效名称(全名=包名.类名)
  2. 这个类型直接父类的完整有效名(对于interface或是java.lang.object,都没有父类)
  3. 这个类型的修饰符(public,abstract,final的某个子集)
  4. 这个类型直接接口的一个有序列表
  • 域(Field)信息
    1.JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。
    2.域的相关信息包括:域名称、域类型、域修饰符(public,private,protected,static,final,volatile,transient的某个子集)
  • 方法(Method)信息
  1. 方法名称
  2. 方法的返回类型(或void)
  3. 方法参数的数量和类型(按顺序)
  4. 方法的修饰符(public,private,protected,static,final,synchronized,native,abstract的一个子集)
  5. 方法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract和native方法除外)
  6. 异常表(abstract和native方法除外)
    ○ 每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引

运行时常量池 VS 常量池

  • 一个java源文件中的类、接口,编译后产生一个字节码文件。而Java中的字节码需要数据支持,通常这种数据会很大以至于不能直接存到字节码里,换另一种方式,可以存到常量池,这个字节码包含了指向常量池的引用。在动态链接的时候会用到运行时常量池。
  • 常量池可以看做是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型。用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
  • 运行时常量池(Runtime Constant Pool)是方法区的一部分。
  • 运行时常量池中包含多种不同的常量,包括编译期就已经明确的数值字面量,也包括到运行期解析后才能够获得的方法或者字段引用。此时不再是常量池中的符号地址了,这里换为真实地址。

对象实例化及直接内存

对象实例化

在这里插入图片描述

  • 判断对象对应的类是否加载、链接、初始化
    虚拟机遇到一条new指令,首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载,解析和初始化(即判断类元信息是否存在)。
    如果没有,那么在双亲委派模式下,使用当前类加载器以ClassLoader + 包名 + 类名为key进行查找对应的 .class文件;
    ● 如果没有找到文件,则抛出ClassNotFoundException异常
    ● 如果找到,则进行类加载,并生成对应的Class对象
  • 为对象分配内存:首先计算对象占用空间的大小,接着在堆中划分一块内存给新对象。如果实例成员变量是引用变量,仅分配引用变量空间即可,即4个字节大小。
  • 设置对象的对象头:将对象的所属类(即类的元数据信息)、对象的HashCode和对象的GC信息、锁信息等数据存储在对象的对象头中。这个过程的具体设置方式取决于JVM实现。

对象内存布局

在这里插入图片描述

StringTable

  • 在Java中创建对象是一件挺耗费性能的事,而且又经常使用相同的String对象,那么创建这些相同的对象就白白浪费性能了。StringTable叫做字符串常量池,用于存放字符串常量,这样当我们使用相同的字符串对象时,就可以直接从StringTable中获取而不用重新创建对象
  • 字符串池可以避免重复创建字符串对象。
  • 常量池中的字符串仅是符号,第一次用到时才变为对象。String s1 = "abc"这样声明的字符串会放入字符串池中,String s1 = new String(“abcd”)会在字符串池有一个"abcd"的字符串对象,堆中也有1个,2个不同。
  • 它的结构为hash表结构,相同的字符串只存在一份。
  • 使用-XX:StringTablesize可设置StringTable的长度。在JDK8中,设置StringTable长度的话,1009是可以设置的最小值 。

String基本特性

● String:字符串,使用一对""引起来表示
● String声明为final的,不可被继承
● String实现了Serializable接口:表示字符串是支持序列化的。
● String实现了Comparable接口:表示string可以比较大小
● String在jdk8及以前内部定义了final char[] value用于存储字符串数据。JDK9时改为byte[]

  • String:代表不可变的字符序列。简称:不可变性。
    ● 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
    ● 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
    ● 当调用string的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。

String的内存分配

  • 在Java语言中有8种基本数据类型和一种比较特殊的类型String。这些类型为了使它们在运行过程中速度更快、更节省内存,都提供了一种常量池的概念。常量池就类似一个Java系统级别提供的缓存。
  • Java 7中 Oracle的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到Java堆内。
    在这里插入图片描述

字符串拼接

  • 对字符串进行5万次拼接运行时长
    在这里插入图片描述

垃圾回收概述及算法

垃圾

  • 垃圾是指在运行程序中没有任何指针指向的对象,这个对象就是需要被回收的垃圾。
  • 如果不及时对内存中的垃圾进行清理,那么,这些垃圾对象所占的内存空间会一直保留到应用程序的结束,被保留的空间无法被其它对象使用,甚至可能导致内存溢出。因为不断地分配内存空间而不进行回收,就好像不停地生产生活垃圾而从来不打扫一样。
  • GC主要关注的区域:GC主要关注于 方法区 和堆中的垃圾收集。可以对年轻代回收,也可以对老年代回收,甚至是全栈和方法区的回收,其中,Java堆是垃圾收集器的工作重点。从次数上讲:频繁收集Young区、较少收集Old区、基本不收集Perm区(元空间)。

垃圾回收相关算法

  • 在GC执行垃圾回收之前,首先需要区分出内存中哪些是存活对象,哪些是已经死亡的对象。只有被标记为己经死亡的对象,GC才会在执行垃圾回收时,释放掉其所占用的内存空间,因此这个过程我们可以称为垃圾标记阶段。
  • 判断对象存活一般有两种方式:引用计数算法和可达性分析算法。

对象的finalization机制

  • java语言提供了对象终止(finalization)机制来允许开发人员提供对象被销毁之前的自定义处理逻辑。
  • 永远不要主动调用某个对象的finalize()方法I应该交给垃圾回收机制调用。理由包括下面三点:
    ● 在finalize()时可能会导致对象复活。
    ● finalize()方法的执行时间是没有保障的,它完全由GC线程决定,极端情况下,若不发生GC,则finalize()方法将没有执行机会。
    ● 一个糟糕的finalize()会严重影响Gc的性能。

垃圾回收相关概念

相关概念

System.gc()
  • 通过system.gc()或者Runtime.getRuntime().gc() 的调用,会显式触发Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。
  • 无法保证对垃圾收集器的调用。不能确保立即生效。
  • 般情况下,垃圾回收应该是自动进行的,无须手动触发,否则就太过于麻烦了。
内存溢出(OOM)
  • OOM 全称 “Out Of Memory”,表示内存耗尽。当 JVM 因为没有足够的内存来为对象分配空间,并且垃圾回收器也已经没有空间可回收时,就会抛出这个错误。出现 OOM,一般由这些问题引起,分配过少:JVM 初始化内存小,业务使用了大量内存;或者不同 JVM 区域分配内存不合理。代码漏洞:某一个对象被频繁申请,不用了之后却没有被释放,导致内存耗尽
  • 内存溢出:申请的内存超出了 JVM 能提供的内存大小,此时称之为溢出。
  • 内存泄漏持续存在,最后一定会溢出,两者是因果关系
  • 要解决OOM异常或heap space的异常,一般的手段是首先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)
  • 如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链。于是就能找到泄漏对象是通过怎样的路径与GCRoots相关联并导致垃圾收集器无法自动回收它们的。掌握了泄漏对象的类型信息,以及GCRoots引用链的信息,就可以比较准确地定位出泄漏代码的位置。
  • 如果不存在内存泄漏,换句话说就是内存中的对象确实都还必须存活着,那就应当检查虚拟机的堆参数(-Xmx与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。
内存泄露(Memory Leak)
  • 申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了。因为申请者不用了,而又不能被虚拟机分配给别人用。内存泄漏最终将导致内存溢出。
  • 内存泄露举例:
    1.单例模式
    单例的生命周期和应用程序是一样长的,所以单例程序中,如果持有对外部对象的引用的话,那么这个外部对象是不能被回收的,则会导致内存泄漏的产生。
    2.一些提供close的资源未关闭导致内存泄漏
    数据库连接(dataSourse.getConnection() ),网络连接(socket)和io连接必须手动close,否则是不能被回收的。
Stop The World(stw)
  • GC事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应。
  • STW是JVM在后台自动发起和自动完成的。在用户不可见的情况下,把用户正常的工作线程全部停掉。垃圾回收器越来越优秀,回收效率越来越高,尽可能地缩短了暂停时间。
  • 开发中不要用System.gc() 会导致Stop-the-World的发生。

引用

强引用
  • 永远不会回收。
  • 普通系统99%以上都是强引用,也就是我们最常见的普通对象引用,也是默认的引用类型类似“Object obj = new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
  • 强引用的对象是可触及的,垃圾收集器就永远不会回收掉被引用的对象。强引用是造成Java内存泄漏的主要原因之一。
软引用
  • 内存不足即回收。
  • 软引用是用来描述一些还有用,但非必需的对象。在系统将要发生内存溢出之前,将会把这些对象列入回收范围之中进行第二次回收。如果这次回收后还没有足够的内存,才会抛出内存溢出异常。
  • 比如null值。
Object obj = new Object(); // 声明强引用
SoftReference<Object> sf = new SoftReference<>(obj);
obj = null; //销毁强引用
弱引用
  • 发现即回收。
  • 弱引用也是用来描述那些非必需对象,只被弱引用关联的对象只能生存到下一次垃圾收集发生为止。在系统GC时,只要发现弱引用,不管系统堆空间使用是否充足,都会回收掉只被弱引用关联的对象。
Object obj = new Object(); // 声明强引用
WeakReference<Object> sf = new WeakReference<>(obj);
obj = null; //销毁强引用
虚引用
  • 对象回收跟踪。
  • 一个对象是否有虚引用的存在,完全不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它和没有引用几乎是一样的,随时都可能被垃圾回收器回收。
  • 为一个对象设置虚引用关联的唯一目的在于跟踪垃圾回收过程。比如:能在这个对象被收集器回收时收到一个系统通知。由于虚引用可以跟踪对象的回收时间,因此,也可以将一些资源释放操作放置在虚引用中执行和记录。
Object obj = new Object(); // 声明强引用
ReferenceQueue phantomQueue = new ReferenceQueue();
PhantomReference<Object> sf = new PhantomReference<>(obj, phantomQueue);
obj = null;

垃圾回收器

  • HotSpot有这么多的垃圾回收器,Serial GC、Parallel GC、Concurrent Mark Sweep GC这三个Gc有什么不同:
    ● 如果你想要最小化地使用内存和并行开销,请选Serial GC;
    ● 如果你想要最大化应用程序的吞吐量,请选Parallel GC;
    ● 如果你想要最小化GC的中断或停顿时间,请选CMS GC。

字节码与类的加载

class文件结构

字节码指令集

类的加载过程(类的生命周期)详解

再谈类的加载器

性能监控与调优

诊断工具

  • JDK自带:
    ● jconsole:JDK自带的可视化监控工具。查看Java应用程序的运行概况、监控堆信息、永久区(或元空间)使用情况、类加载情况等
    ● Visual VM:Visual VM是一个工具,它提供了一个可视界面,用于查看Java虚拟机上运行的基于Java技术的应用程序的详细信息。
    ● JMC:Java Mission Control,内置Java Flight Recorder。能够以极低的性能开销收集Java虚拟机的性能数据。
  • 第三方工具
    ● MAT:MAT(Memory Analyzer Tool)是基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗
    ● JProfiler:商业软件,需要付费。功能强大。
    ● Arthas
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JVMJava虚拟机)的老年代是指JVM中的内存区域,用于存储生命周期较长的对象。在JVM中,内存被划分为多个不同的区域,其中包括年轻代和老年代。老年代主要用于存放经过多次垃圾回收后幸存下来的对象。 老年代的对象有以下特点: 1. 生命周期较长:老年代存放的对象通常是经过多次垃圾回收后仍然存活的对象,它们的生命周期相对较长。 2. 大对象:老年代通常会存放较大的对象,例如大型集合、数组等。这些对象占用的内存空间较多。 3. 垃圾回收较少:由于老年代存放的对象生命周期较长,因此老年代的垃圾回收相对较少。但是当老年代空间不足时,JVM会触发一次较为耗时的Full GC来进行垃圾回收。 为了优化老年代的性能和空间利用率,JVM引入了一些技术: 1. 分代回收:JVM采用分代回收策略,将内存划分为年轻代和老年代,以便更好地适应不同对象的生命周期。 2. 大对象处理:JVM针对较大的对象引入了特殊的处理方式,例如将大对象直接分配到老年代。 3. 垃圾回收算法:JVM使用不同的垃圾回收算法来对老年代进行垃圾回收,例如标记-清除算法、标记-整理算法等。 总之,老年代是JVM中用于存放生命周期较长的对象的内存区域。通过采用分代回收策略和针对大对象的处理方式,JVM能够更好地管理和优化老年代的性能和空间利用率。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值