运行时数据区构造
堆、栈、办法去的交互关系
1、介绍:
《Java 虚拟机标准》中明确阐明:“只管所有的办法区在逻辑上是属于堆的一部分,但一些简略的实现可能不会抉择去进行垃圾收集或者进行压缩。”但对于 HotSpotJVM 而言,办法区还有一个别名叫做 Non-Heap(非堆), 目标就是要和堆离开。所以,办法区看作是一块独立于 Java 堆的内存空间。
- 办法区(Method Area)与 Java 堆一样,是各个线程共享的内存区域。
- 办法区在 JVM 启动的时候被创立,并且它的理论的物理内存空间中和 Java 堆区一样都能够是不间断的。
- 办法区的大小,跟堆空间一样,能够抉择固定大小或者可扩大。
-
办法区的大小决定了零碎能够保留多少个类,如果零碎定义了太多的类,导致办法区溢出,虚拟机同样会抛出内存溢出谬误:java.lang.OutofMemoryError:PermGen space(8 前)或者 java.lang.OutofMemoryError:Metaspace(8 以及当前)
* 加载过多第三方 jar 包;Tomcat 部署我的项目过多;大量动静的生成反射类
- 敞开 JVM 就会开释这个区域的内存。
别称:jdk7 及以前(永恒代),jdk8 及当前(元空间)
演变
元空间的实质和永恒代相似,都是对 JVM 标准中办法区的实现。不过元空间与永恒代最大的区别在于:元空间不在虚拟机设置的内存中,而是应用本地内存。
永恒代、元空间二者并不只是名字变了,内部结构也调整了。
2、设置办法区内存大小
- 元数据区大小能够应用参数
-XX:MetaspaceSize
和-XX:MaxMetaspaceSize
指定,代替上述原有的两个参数。 - 默认值依赖于平台。windows 下,-XX:MetaspaceSize 是 21M,-XX:MaxMetaspaceSize 的值是 -1, 即没有限度。
- 与永恒代不同,如果不指定大小,默认状况下,虚构机会耗尽所有的可用零碎内存。如果元数据区产生溢出,虚拟机一样会抛出异样 OutOfMemoryError:Metaspace
- -XX:MetaspaceSize: 设置初始的元空间大小。对于一个 64 位的服务器端 JVM 来说,其默认的 XX:MetaspaceSize 值为 21MB。这就是初始的高水位线,一旦涉及这个水位线,Full GC 将会被触发并卸载没用的类(即这些类对应的类加载器不再存活)而后这个高水位线将会重置。新的高水位线的值取决于 GC 后开释了多少元空间。如果开释的空间有余,那么在不超过 MaxMetaspaceSize 时,适当进步该值。如果开释空间过多,则适当升高该值。
- 如果初始化的高水位线设置过低,上述高水位线调整状况会产生很屡次。通过垃圾回收器的日志能够察看到 Fu11 GC 屡次调用。为了防止频繁地 GC, 倡议将 -XX:MetaspaceSize 设置为一个绝对较高的值。
如何解决这些 OOM?
1、要解决 OOM 异样或 heap space 的异样,个别的伎俩是首先通过内存映像剖析工具 (如 Eclipse Memory Analyzer) 对 dump 进去的堆转储快照进行剖析,重点是确认内存中的对象是否是必要的,也就是要先分分明到底是呈现了内存透露(MemoryLeak)还是内存溢出(Memory Overflow)。
2、如果是内存透露,可进一步通过工具查看透露对象到 GC Roots 的援用链。于是就能找到透露对象是通过怎么的门路与 GC Roots 相关联并导致垃圾收集器无奈主动回收它们的。把握了透露对象的类型信息,以及 GC Roots 援用链的信息,就能够比拟精确地定位出透露代码的地位。
3、如果不存在内存透露,换句话说就是内存中的对象的确都还必须存活着,那就该当查看虚拟机的堆参数(-Xmx 与 -Xms), 与机器物理内存比照看是否还能够调大,从代码上查看是否存在某些对象生命周期过长、持有状态工夫过长的状况,尝试缩小程序运行期的内存耗费
3、办法区内存构造
3.1、办法区所存储的内容:
1、类型信息
对每个加载的类型(类 class、接口 interface、枚举 enum、注解 annotation),JVM 在办法区中存储以下类型信息:
①这个类型的残缺无效名称(全名 = 包名. 类名)
②这个类型间接父类的残缺无效名(对于 interface 或是 java.lang.object, 都没有父类)
③这个类型的修饰符(public,abstract,final 的某个子集)
④这个类型间接接口的一个有序列表
2、域信息
JVM 必须在办法区中保留类型的所有域的相干信息以及域的申明程序。
域的相干信息包含:域名称、域类型、域修饰符(public,private,protected, static, final, volatile, transient 的某个子集)
3、办法信息
JVM 必须保留所有办法的以下信息,同域信息一样包含申明程序:
- 办法名称
- 办法的返回类型(或 void)
- 办法参数的数量和类型(按程序)
- 办法的修饰符(public,private,protected,static,final,synchronized,native,abstract 的一个子集)
- 办法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract 和 native 办法除外)
- 异样表(abstract 和 native 办法除外):每个异样解决的开始地位、完结地位、代码解决在程序计数器中的偏移地址、被捕捉的异样类的常量池索引
查看 命令行输出 javap -v -p(蕴含 private 权限)xxx.class > xxx.txt
示例:
public class Test extends HashMap implements Serializable {
private String name = "";
private int x = 1;
public Test(String name) {this.name = name;}
public static void main(String[] args) {Test haha = new Test(null);
int nameLength = haha.getNameLength();
System.out.println(nameLength);
}
public int getNameLength() {
int y = 0;
try {y = name.length();
} catch (NullPointerException e) {System.out.println("空指针异样");
e.printStackTrace();}
return y;
}
}
Classfile /D:/ideaFiles/Algorithm/out/production/Algorithm/com/lx/mySort/Test.class
Last modified 2020-7-29; size 1145 bytes
MD5 checksum 8f9825153f3fa6f2042785c0df59703b
Compiled from "Test.java"
// 类信息
public class com.lx.mySort.Test extends java.util.HashMap implements java.io.Serializable
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #15.#44 // java/util/HashMap."<init>":()V
#2 = String #45 //
...
{
// 域信息
private java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE
private int x;
descriptor: I
flags: ACC_PRIVATE
// 办法信息
...
public int getNameLength();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: iconst_0
1: istore_1
2: aload_0
3: getfield #3 // Field name:Ljava/lang/String;
6: invokevirtual #10 // Method java/lang/String.length:()I
9: istore_1
10: goto 26
13: astore_2
14: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
17: ldc #12 // String 空指针异样
19: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
22: aload_2
23: invokevirtual #14 // Method java/lang/NullPointerException.printStackTrace:()V
26: iload_1
27: ireturn
// 异样表
Exception table:
from to target type
2 10 13 Class java/lang/NullPointerException
LineNumberTable:
line 26: 0
line 28: 2
line 32: 10
line 29: 13
line 30: 14
line 31: 22
line 33: 26
LocalVariableTable:
Start Length Slot Name Signature
14 12 2 e Ljava/lang/NullPointerException;
0 28 0 this Lcom/lx/mySort/Test;
2 26 1 y I
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 13
locals = [class com/lx/mySort/Test, int]
stack = [class java/lang/NullPointerException]
frame_type = 12 /* same */
}
SourceFile: "Test.java"
4、动态变量
- non-final 的类变量
static 动态变量:加载时筹备阶段(赋默认值)、初始化阶段赋给定值
- 全局常量
static final:编译时(筹备阶段)赋给定值
5、运行时常量池
常量池
常量池所在位置
一个无效的字节码文件中除了蕴含类的版本信息、字段、办法以及接口等形容信息外,还蕴含一项信息那就是常量池表(Constant Pool Table), 包含各种字面量和对类型、域和办法的符号援用。
常量池的作用:
一个 java 源文件中的类、接口,编译后产生一个字节码文件。而 Java 中的字节码须要数据反对,通常这种数据会很大以至于不能间接存到字节码里,换另一种形式,能够存到常量池,这个字节码蕴含了指向常量池的援用。在动静链接的时候会用到运行时常量池,之前有介绍。
常量池存储的数据
- 数量值
- 字符串值
- 类援用
- 字段援用
- 办法援用
运行时常量池
- 运行时常量池(Runtime Constant Pool)是办法区的一部分。
- 常量池表(Constant Pool Table)是 class 文件的一部分。
- 运行时常量池,在加载类和接口到虚拟机后,就会创立对应的运行时常量池。
- JVM 为每个已加载的类型(类或接口)都保护一个常量池。
-
运行时常量池中蕴含多种不同的常量,包含编译期就曾经明确的数值字面量,也包含到运行期解析后才可能取得的办法或者字段援用。此时不再是常量池中的符号地址了,这里换为实在地址。
* 运行时常量池,绝对于 class 文件常量池的另一重要特色是:具备动态性。
- 当创立类或接口的运行时常量池时,如果结构运行时常量池所需的内存空间超过了办法区所能提供的最大值,则 JVM 会抛 OutOfMemoryError 异样。
4、演进过程
永恒代为什么会被元空间替换
- 永恒代空间大小很难确定,太小容易 GC/OOM 异样,太大占用内存(元空间并不在虚拟机中、而是应用本地内存,大小仅受本地内存限度)
- 永恒代调优艰难
- 垃圾回收频率低
动态变量放到哪里?
堆空间里的永恒代(7 及后是堆)
5、办法区的垃圾回收
次要回收:
1、常量池中废除的常量:字面量和符号援用 …(没用被援用,则能够进行回收)
2、不再应用的类型(同时满足以下三个条件的类可被容许回收):
1)该类的所有实例都被回收了,即 Java 堆中不存在该类及其任何派生的子类的实例
2)该类的类加载器曾经被回收了(除非精心设计,否则很难实现,如 OSGI,JSP 的重加载等)
3)该类对象对应的 java.lang.Class 对象没有在任何中央援用、无奈在任何中央通过反射拜访到该类的办法
对于是否要对类型进行回收,HotSpot 虚拟机提供了 -Xnoclassgc 参数进行管制,还能够应用 -verbose:class 以及
-XX:+TraceClass-Loading、-XX:+TraceClassUnLoading 查看类加载和卸载信息
5、小结
6、对象实例化
初始化:
- 默认初始化
- 显示初始化 / 代码块初始化 / 结构器初始化
7、对象的内存布局:
示例:
8、对象的拜访定位
8.1、拜访对象的形式:
-
句柄拜访
reference 中存储稳固句柄地址,对象被挪动(垃圾回收时常见)时只会扭转句柄池中到对象示例数据的指针即可,reference 不必批改
-
间接指针(HotSpot 采纳)
内存绝对于较小
9、间接内存
- 不是虚拟机运行时数据区的一部分,也不是《Java 虚拟机标准》中定义的内存区域。
- 间接内存是在 Java 堆外的、间接向零碎申请的内存区间。
- 来源于 NIO, 通过存在堆中的 DirectByteBuffer 操作 Native 内存
-
通常,拜访间接内存的速度会优于 Java 堆。即读写性能高。
* 因而出于性能思考,读写频繁的场合可能会思考应用间接内存。* Java 的 NIO 库容许 Java 程序应用间接内存,用于数据缓冲区
关注公众号:java 宝典