NIO与JVM基本概念

NIO

NIO: New IO。 Non-Blocking IO(非阻塞)。

NIO 是JDK1.4的时候出现了⼀个新的IO, ⽤来替代传统的IO流。 NIO与IO有着相同的功能, 但是操作的⽅法不同。Java提供了⼀些改进输⼊/输出处理的新功能,这些新功能被统称为新IO(New IO 简称NIO),新增了许多⽤于处理输⼊/输出的类,这些类都被放在java.nio包以及⼦包下,并对原java.io中的很多类都以NIO为基础进⾏改写,新增了满⾜NIO的功能

NIO是基于通道(Channel), ⾯向缓冲区(Buffer)的。

在JDK1.7的时候, 为NIO添加了⼀些新的特定。 被称为 NIO.2

Java NIO 核心组成部分:

Channels:通道 Buffer:缓冲区 Selectors:选择器

Channels和Buffer是新IO中的两个核⼼对象,Channel是对传统的输⼊/输出系统的模拟,在新IO系统中所有的数据都需要通过通道传输。

Channels与传统的InputStream,OutputStrem最⼤的区别在于它提供了⼀个map()⽅法,通过该map()⽅法可以直接将"⼀块数据"映射到内存中,如果说传统的输⼊/输出系统是⾯向流处理,则新IO则是⾯向块的处理

Buffer可以被理解为⼀个容器(缓冲区,数组),发送到Channel中的所有对象都必须⾸先
放到Buffer中,⽽从Channel中读取的数据也必须放到Buffer中,也就是说数据可以从Channel读取到Buffer中,也可以从Buffer写到Channel中

和传统的IO的区别

1.IO⾯向流,NIO⾯向缓冲区
Java IO⾯向流意味着每次从流中读⼀个或多个字节,直⾄读取所有字节,它们没有被缓存在任何地⽅。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到⼀个缓冲区。 Java NIO的缓冲导向⽅法略有不同。数据读取到⼀个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性.
2.IO是阻塞式的,NIO有⾮阻塞式的
Java IO的各种流是阻塞的。这意味着,当⼀个线程调⽤read() 或 write()时,该线程被阻塞,直到有⼀些数据被读取,或数据完全写⼊。该线程在此期间不能再⼲任何事情了。Java NIO的⾮阻塞模式,使⼀个线程从某通道发送请求读取数据,但是它仅能得到⽬前可⽤的数据,如果⽬前没有数据可⽤时,就什么都不会获取,⽽不是保持线程阻塞所以直⾄数据变的可以读取之前,该线程可以继续做其他的事情。 ⾮阻塞写也是如此。
3.IO没有选择器,NIO有选择器
Java NIO的选择器允许⼀个单独的线程来监视多个输⼊通道,你可以注册多个通道使⽤⼀个选择器,然后使⽤⼀个单独的线程来“选择”通道:这些通道⾥已经有可以处理的输⼊,或者选择已准备写⼊的通道。这种选择机制,使得⼀个单独的线程很容易来管理多个通道.

JVM是运⾏在操作系统之上的,它与硬件没有直接的交集。 JVM直接翻译为Java虚拟机但实际应该是Java虚拟机规范。

三种JVM

Sun公司的HotSpot、 BEA公司的JRockit、 IBM公司的J9 VM

提起HotSpot VM,相信所有Java程序员都知道,它是Sun JDK和OpenJDK中所带的虚拟机,也是⽬前使⽤范围最⼴的Java虚拟机。但不⼀定所有⼈都知道的是,这个⽬前看起来“⾎统纯正”的虚拟机在最初并⾮由Sun公司开发, ⽽是由⼀家名为“Longview,Technologies”的⼩公司设计的; 甚⾄这个虚拟机最初并⾮是为Java语⾔⽽开发的,它来源于Strongtalk VM,⽽这款虚拟机中相当多的技术⼜是来源于⼀款⽀持Self语⾔实现“达到C语⾔50%以上的执⾏效率”的⽬标⽽设计的虚拟机,Sun公司注意到了这款虚拟机在JIT编译上有许多优秀的理念和实际效果,在1997年收购了LongviewTechnologies公司,从⽽获得了HotSpot VM。

BEA公司02年从Appeal Virtual Machines收购获得,专注于服务端应⽤,曾经号称世界上速度最快的虚拟机在2008年和2009年,Oracle公司分别收购了BEA公司和Sun公司,这样Oracle就同时拥有了两款优秀的Java虚拟机: JRockit VM和HotSpotVM。Oracle公司宣布在不久的将来(⼤约应在发布JDK 8的时候)会完成这两款虚拟机的整合⼯作,使之优势互补。整合的⽅式⼤致上是在HotSpot的基础上,移植JRockit的优秀特性,譬如使⽤JRockit的垃圾回收器与MissionControl服务,使⽤HotSpot的JIT编译器与混合的运⾏时系统。

J9是IBM公司开发的虚拟机,其为IBM公司各种产品的执⾏平台。

Buffer缓冲区

缓冲区的概念

其实是⼀个⽤来存储基本数据类型的⼀个容器, 类似于⼀个数组。缓冲区, 可以按照存储的数据类型不同, 将缓冲区分为:
ByteBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer、CharBuffer
但是, 要注意, 并没有BooleanBuffer!
这些缓冲区, 虽然是不同的类, 但是他们拥有的相同的属性和⽅法。 因为他们都继承⾃相同的⽗类 Buffer。

Buffer的⼏个属性

  • capacity: 容量。 代表⼀个缓冲区的最⼤的容量, 缓冲区⼀旦开辟完成, 将⽆法修改。
  • limit: 限制。 表示缓冲区中有多少数据可以操作。
  • position: 位置。 表示当前要操作缓冲区中的哪⼀个下标的数据。
  • mark: 标记。 在缓冲区中设计⼀个标记, 配合 reset() ⽅法使⽤, 修改position的值。

mark <= position <= limit <= capacity

Buffer常⽤的⽅法

在这里插入图片描述

  • 在向缓冲区中写数据的时候, 要注意: 不要超出缓冲区的范围。 如果超出范围了, 会出现BufferOverflowException异常, 且本次put的所有数据都不会存⼊到缓冲区中。

缓冲区, 其实有两种模式: 分别是读模式和写模式。
读模式: 就是从缓冲区中读取数据。
写模式: 就是将数据写⼊到缓冲区。
⼀个刚刚被开辟的缓冲区, 默认处于写模式。

缓冲区的详解

其实, 缓冲区中, 并没有所谓的读模式和写模式。 其实所谓“读模式”和“写模式”,只是逻辑上的区分。⼀个处于“读模式”下的缓冲区, 依然可以写数据。 ⼀个处理“写模式”下的缓冲区,依然可以读取数据。上述⽅法中, 基本所有的⽅法, 都是围绕着缓冲区中的⼏个属性进⾏的。

/*
* 在创建buffer对象的时候传递的参数就是capacity
* 容量为1024的缓冲区
* 此时buffer的limit和capacity都为1024
* 此时的position是0
*/
//开辟容量1024字节
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("hello".getBytes());
System.out.println(buffer.position());//5 
//position是5,说明写⼊了5个字节,position指向的是前内容的结尾,⽅便接着往下写
buffer.put("world".getBytes());
System.out.println(buffer.position());//10
//切换为读模式
buffer.flip();
/*
*这⼀步很重要 flip可以理解为模式切换 之前的代码实现的是写⼊操作
*当调⽤这个⽅法后就变成读取操作,那么position和limit的值就要发⽣变换
*此时capacity为1024不变
*此时limit就移动到原来position所在的位置,相当于把buffer中没有数据的空间"封印起来"从⽽避免读取Buffer数据的时候读到null值
*相当于 limit = position limit = 10
*此时position的值相当于 position = 0
*/
/*
* clear():
* API中的意思是清空缓冲区,但是实际是没有删除缓冲区数据的
* ⽽是将缓冲区中limit和position恢复到初始状态
* 即limit和capacity都为1024 position是0
* 此时可以完成写⼊模式
*/

直接缓冲区

⾮直接缓冲区, 是在JVM中开辟的空间。 allocate(int capacity)
直接缓冲区, 是直接在物理内存上开辟的空间。 allocateDirect(int capacity)

对于⼀个已经存在的缓冲区, 可以使⽤ isDirect() ⽅法, 判断是否是直接缓冲区。 这个⽅法的返回值类型是boolean类型的, 如果是true, 就表示是⼀个直接缓冲区。 如果是false, 就表示不是⼀个直接缓冲区。

Channel通道

通道的简介

通道Channel, 就是⽂件和程序之间的连接。 在NIO中, 数据的传递, 是由缓冲区实现的, 通道本身并不负责数据的传递。

通道在 java.nio.channels 包中, 常⻅的Channel⼦类:

  • 本地⽂件: FileChannel
  • ⽹络⽂件: SocketChannel, ServerSocketChannel, DatagramChannel

Channel类似于传统的流对象,但与传统的流对象有两个主要的区别:

  • Channel可以直接将我指定⽂件的部分或全部直接映射成Buffer
  • 程序不能直接访问Channel中的数据,包括读取,写⼊都不⾏.Channel只能与Buffer进⾏交互

通道的打开⽅式

  1. 可以使⽤⽀持通道的类, getChannel()⽅法获取通道。

    本地⽂件: FileInputStream、FileOutputStream

    ⽹络⽂件: Socket、ServerSocket、DatagramSocket

  2. 在NIO.2中, 使⽤FileChannel.open()⽅法打开通道。

  3. 在NIO.2中, 使⽤Files⼯具类newXXX()⽅法打开通道。

使⽤FileChannel读写数据

在使⽤FileChannel之前,必须先打开它。但是,我们⽆法直接打开⼀个FileChannel,需要通过使⽤⼀个InputStream、OutputStream的getChannel()⽅法来返回对应的Channel

  • 写数据
//创建⽂件写出流
FileOutputStream fileOutputStream = new FileOutputStream("temp1.txt");
//获取通道
FileChannel fileChannel = fileOutputStream.getChannel();
//创建缓冲流
ByteBuffer buffer = ByteBuffer.allocate(1024);
//向缓冲流中放⼊数据
buffer.put("helloworld".getBytes());
//转换模式为读取内容
buffer.flip();
//利⽤通道写⼊
fileChannel.write(buffer);
//关闭资源
fileChannel.close();
fileOutputStream.close();
  • 读数据
//创建⽂件输⼊流
FileInputStream fileInputStream = new FileInputStream("temp1.txt");
//获取通道
FileChannel fileChannel = fileInputStream.getChannel();
//创建缓冲流对象
ByteBuffer buffer = ByteBuffer.allocate(1024);
//利⽤通道读取内容
int num = fileChannel.read(buffer);
System.out.println(num);
//转换模式为读取模式
buffer.flip();
//利⽤缓冲流读取数据到控制台
System.out.println(new String(buffer.array(),0,num));
//关闭资源
fileChannel.close();
fileInputStream.close();

NIO2

Java7对原有NIO进⾏了重⼤改进,改进主要包括如下两个⽅⾯的内容

1.提供了全⾯的⽂件IO和⽂件系统访问⽀持
2.基于异步Channel的IO

Path、Paths和Files

之前学习中学习过File类来访问⽂件系统,但是File类的功能⽐较有限,NIO.2为了弥补这种不⾜,引⼊了⼀个Path接⼝还提供了Files和Paths两个⼯具类,其中Files封装了包含⼤量静态⽅法来操作⽂件,Paths则包含了返回Path的静态⼯⼚⽅法。

Files使⽤示例

// 1. 创建⼀级⽂件夹(如果这个⽂件夹存在,会抛异常)
Files.createDirectory(Paths.get("C:\\Users\\luds\\Desktop\\abc"));
// 2. 创建多级⽂件夹(如果需要创建的⽂件夹存在,不会抛出异常)
Files.createDirectories(Paths.get("C:\\Users\\luds\\Desktop\\abc\\a\\b\\c"));
// 3. 创建⼀个⽂件(如果这个⽂件已存在,重复创建会异常)
Files.createFile(Paths.get("C:\\Users\\luds\\Desktop\\abc\\file"));
// 4. 删除⼀个⽂件或者空⽂件夹(如果要删除的⽂件或空⽂件夹不存在,会抛异常;如果删除的不是⼀个空⽂件夹也会抛异常)
Files.delete(Paths.get("C:\\Users\\luds\\Desktop\\abc\\file"));
// 5. 删除⼀个⽂件或者空⽂件夹(如果删除失败,不会抛异常,返回false)
Files.deleteIfExists(Paths.get("C:\\Users\\luds\\Desktop\\abc\\file"));
// 6. 移动⽂件(重命名)
Files.move(Paths.get("file\\day28\\source"),Paths.get("file\\day28\\dst\\source"));
// 7. 拷⻉⽂件
Files.copy(Paths.get("file\\day28\\dst\\source"),Paths.get("file\\day28\\source"));

NIO2使用示例

//构建Path对象的两种⽅式
//传⼀个路径
Path path1 = Paths.get("D:\\123");
//第⼀个参数是盘符 ,第⼆个参数是可变参数 下⾯有多少⽂件路径就写多少
Path path2 = Paths.get("D:\\", "123","456.txt");
//Path是结合Files⼯具类使⽤的
//创建⽬录
Files.createDirectories(path1);
//判断⽂件是否存在
if(!Files.exists(path2)) {
//创建⽂件
    Files.createFile(path2);
}
//复制⽂件
//第⼀个参数原⽂件路径, 第⼆个参数⽬标⽂件路径
// Files.copy(new FileInputStream(new
File("D:\\123\\456.txt")), Paths.get("D:\\", "123","222.txt"));
// Files.copy(path2, new FileOutputStream(new File("D:\\123\\789.txt")));
// Files.copy(path2, Paths.get("D:\\", "123","111.txt"));
//⼀次读取⽂件中所有的⾏
List<String> readAllLines = Files.readAllLines(Paths.get("src/com/qiangfeng/test/Demo1.java"));
for (String str : readAllLines) {
    System.out.println("haha:"+str);
}
//将集合中的内容写⼊到⽂件中
Files.write(Paths.get("D:\\", "123","Demo.java"),readAllLines);
/*
static BufferedReader newBufferedReader(Path path)
打开⼀个⽂件进⾏读取,返回⼀个 BufferedReader以有效⽅式从⽂件中
读取⽂本。
static BufferedReader newBufferedReader(Path path,
Charset cs)
打开⼀个⽂件进⾏读取,返回⼀个 BufferedReader可以⽤来有效⽅式从
⽂件中读取⽂本。
static BufferedWriter newBufferedWriter(Path path,
Charset cs, OpenOption... options)
打开或创建⼀个⽂件写⼊,返回⼀个 BufferedWriter,可以有效的⽅式
将⽂件写⼊⽂本。
static BufferedWriter newBufferedWriter(Path path,
OpenOption... options)
打开或创建⼀个⽂件写⼊,返回⼀个 BufferedWriter能够以有效的⽅式
的⽂件写⼊⽂本。
直接创建缓冲流对象 可以执⾏ 字符集 和访问权限
*/

JVM

类加载器ClassLoader

类加载器, 负责加载class⽂件。 class⽂件在⽂件开头有特定的⽂件标示。ClassLoader只负责class⽂件的加载, ⾄于它是否可以运⾏, 则由ExecutionEngine决定。

在这里插入图片描述

Car.class 相当于是我们编写的类模板, 封装着属性和⾏为, 但是.class⽂件是存储在物理内存中的。 我们通过ClassLoader类的加载, 加载到JVM中, 此时可以得到Car Class这⾥的Car。 Class就相当于是JVM中的模板, 那么创建Car实例都是⼀样的。 我们之前学过反射, 那么我们知道通过反射可以获取属性和⾏为, 就是因为我们获取到了JVM中的Car Class。

在这里插入图片描述

启动类加载器:
这个类加载器负责放在 <JAVA_HOME>\lib ⽬录中的, 或者被 -Xbootclasspath 参数所指定的路径中的, 并且是虚拟机识别的类库。 ⽤户⽆法直接使⽤。 就相当于是为什么我们可以通过new创建出来对象。

扩展类加载器:
这个类加载器由 sun.misc.Launcher$AppClassLoader 实现。它负责<JAVA_HOME>\lib\ext ⽬录中的,或者被 java.ext.dirs 系统变量所指定的路径中的所有类库。⽤户可以直接使⽤。

应⽤程序类加载器:
这个类由 sun.misc.Launcher$AppClassLoader 实现。是ClassLoader中getSystemClassLoader() ⽅法的返回值。它负责⽤户路径 ClassPath 所指定的类库。⽤户可以直接使⽤。如果⽤户没有⾃⼰定义类加载器,默认使⽤这个。

⾃定义加载器:
⽤户⾃⼰定义的类加载器(只有做框架才会⽤到)

JVM双亲委派机制和沙箱机制

双亲委派

⼀个类加载器查找class和resource时,是通过“委托模式”进⾏的,它⾸先判断这个class是不是已经加载成功,如果没有的话它并不是⾃⼰进⾏查找,⽽是先通过⽗加载器,然后递归下去,直Bootstrap ClassLoader,如果Bootstrap classloader找到了,直接返回,如果没有找到,则⼀级⼀级返回,最后到达⾃身去查找这些对象。这种机制就叫做双亲委托。

在这里插入图片描述

可以看到2根箭头,蓝⾊的代表类加载器向上委托的⽅向,如果当前的类加载器没有查询到这个class对象已经加载就请求⽗加载器(不⼀定是⽗类)进⾏操作,然后以此类推。直到BootstrapClassLoader。如果Bootstrap ClassLoader也没有加载过此class实例,那么它就会从它指定的路径中去查找,如果查找成功则返回,如果没有查找成功则交给⼦类加载器,也就是ExtClassLoader,这样类似操作直到终点,也就是上图中的红⾊箭头示例。

  1. ⼀个AppClassLoader查找资源时,先看看缓存是否有,缓存有从缓存中获取,否则委托给⽗加载器。
  2. 递归,重复第1部的操作。
  3. 如果ExtClassLoader也没有加载过,则由Bootstrap ClassLoader出⾯,它⾸先查找缓存,如果没有找到的话,就去找⾃⼰的规定的路径下,也就是sun.mic.boot.class下⾯的路径。找到就返回,没有找到,让⼦加载器⾃⼰去找。
  4. Bootstrap ClassLoader如果没有查找成功,则ExtClassLoader⾃⼰在java.ext.dirs路径中去查找,查找成功就返回,查找不成功,再向下让⼦加载器找。
  5. ExtClassLoader查找不成功,AppClassLoader就⾃⼰查找,在java.class.path路径下查找。找到就返回。如果没有找到就让⼦类找,如果没有⼦类会怎么样?
    抛出各种异常。

沙箱机制

沙箱机制也就是双亲委派模型的安全机制。

/**
* 创建⼀个String类包名和类名完全和系统中的String⼀致
*/
public final class String {
    public static void main(String[] args) {
    	System.out.println("谈恋爱吗?做监狱的那种!");
    }
}
// 当我们执⾏的时候回发现:
// 错误: 在类 java.lang.String 中找不到 main ⽅法, 请将 main ⽅法定义
为: public static void main(String[] args) 否则JavaFX应⽤程序类必须
扩展javafx.application.Application
// 代码中是明明有main的为什么会出错就是时双亲委托中的沙箱机制了
// ⾃定义的java.lang.String类永远都不会被加载进内存。
// 因为⾸先是最顶端的类加载器加载系统的java.lang.String 类,最终⾃定义的类加载器⽆法加载java.lang.String类这样⼀来的好处就是可以保证不被恶意的修改系统中原有的类

了解:
双亲委托和沙箱机制不是绝对安全的因为可以写⾃定义ClassLoader,⾃定义的类加载器⾥⾯强制加载⾃定义的 java.lang.String 类,不去通过调⽤⽗加载器 ,完成类的加载. 当ClassLoader加载成功后,Execution Engine执⾏引擎负责解释命令,提交操作系统执⾏。

本地⽅法栈和本地⽅法接⼝

我们可以发现Object类中有很多⽅法是使⽤native修饰,并且没有⽅法体,那说明这些⽅法超出了Java的范围。 所以底层实现需要使⽤JNI—>Java Native Interface。

native修饰的⽅法就是告诉java本身,此时需要调⽤外部的本地类库即C/C++类库来执⾏这个⽅法Native Interface本地接⼝

本地接⼝的作⽤是融合不同的编程语⾔为 Java 所⽤,它的初衷是融合 C/C++程序,Java 诞⽣的时候是 C/C++横⾏的时候,要想⽴⾜,必须有调⽤ C/C++程序,于是就在内存中专⻔开辟了⼀块区域处理标记为native的代码,它的具体做法是 Native Method Stack中登记 native⽅法,在Execution Engine 执⾏时加载native libraies。⽬前该⽅法使⽤的越来越少了,除⾮是与硬件有关的应⽤,⽐如通过Java程序驱动打印机或者Java系统管理⽣产设备,在企业级应⽤中已经⽐较少⻅。因为现在的异构领域间的通信很发达,⽐如可以使⽤ Socket通信,也可以使⽤WebService等等,不多做介绍。

Native Method Stack

它的具体做法是Native Method Stack中登记native⽅法,在Execution Engine 执⾏时加载本地⽅法库。

问题:现在我们发现虚拟机中有Java栈和本地⽅法栈两个栈区,那么我们在创建对象或是声明变量的时候对应的变量或对象是在Java栈还是本地⽅法栈中呢?

Person p = new Person();

那么对象p是在Java栈还是本地⽅法栈?

不带native的进⼊到Java栈中,只有带native的进⼊到本地⽅法栈,所以p对象是在Java栈中,⽽我们平时所说的栈就是Java栈。

PC寄存器

每个线程都有⼀个程序计数器,是线程私有的,就是⼀个指针,指向⽅法区中的⽅法字节码(⽤来存储指向下⼀条指令的地址,也即将要执⾏的指令代码),由执⾏引擎读取下⼀条指令,是⼀个⾮常⼩的内存空间,⼏乎可以忽略不记。

Method Area⽅法区

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

存放在⽅法区的:静态变量+常量+类信息(构造⽅法/接⼝定义)+运⾏时常量池存在⽅法区中

ps:只要是被线程私有独享的⼀律没有会后,只有是线程共享才能有回收

Stack栈是什么

介绍两个数据结构:

  • 栈:先进后出(FILO)
  • 队列:先进先出(FIFO)

在这里插入图片描述

栈也叫栈内存,主管Java程序的运⾏,是在线程创建时创建,它的⽣命期是跟随线程的⽣命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程⼀结束该栈就Over,生命周期和线程⼀致,是线程私有的。

栈中存储什么: 8种基本类型的变量+对象的引⽤变量+实例⽅法都是在函数的栈内存中分配

栈帧中主要保存3 类数据:

  • 本地变量(Local Variables):输⼊参数和输出参数以及⽅法内的变量;
  • 栈操作(Operand Stack):记录出栈、⼊栈的操作;
  • 栈帧数据(Frame Data):包括类⽂件、⽅法等等

栈运⾏原理

栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是⼀个内存区块,是⼀个数据集,是⼀个有关⽅法(Method)和运⾏期数据的数据集,当⼀个⽅法A被调⽤时就产⽣了⼀个栈帧 F1,并被压⼊到栈中。

A⽅法⼜调⽤了 B⽅法,于是产⽣栈帧 F2 也被压⼊栈,
B⽅法⼜调⽤了 C⽅法,于是产⽣栈帧 F3 也被压⼊栈,
……
执⾏完毕后,先弹出F3栈帧,再弹出F2栈帧,再弹出F1栈帧……

StackOverflowError

StackOverflflowError:抛出这个错误是因为递归太深。 其实真正的原因是因为Java线程操作是基于栈的,当调⽤⽅法内部⽅法也就是进⾏⼀次递归的时候就会把当前⽅法压⼊栈直到⽅法内部的⽅法执⾏完全之后,就会返回上⼀个⽅法,也就是出栈操作执⾏上⼀个⽅法。

  • StackOverflflowError:递归过深,递归没有出⼝。
  • OutOfMemoryError:JVM空间溢出,创建对象速度⾼于GC回收速度。

Heap堆

⼀个JVM实例只存在⼀个堆内存,堆内存的⼤⼩是可以调节的。类加载器读取了类⽂件后,需要把类、⽅法、常变量放到堆内存中,保存所有引⽤类型的真实信息,以⽅便执⾏器执⾏,堆内存分为三部分:

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

在这里插入图片描述

新⽣区

新⽣区是类的诞⽣、成⻓、消亡的区域,⼀个类在这⾥产⽣,应⽤,最后被垃圾回收器收集,结束⽣命。新⽣区⼜分为两部分: 伊甸区(Eden space)和幸存者区(Survivor pace) ,所有的类都是在伊甸区被new出来的。幸存区有两个: 0区(Survivor 0 space)和1区(Survivor 1 space)。当伊甸园的空间⽤完时,程序⼜需要创建对象,JVM的垃圾回收器将对伊甸园区进⾏垃圾回收(Minor GC)即轻量垃圾回收,将伊甸园区中的不再被其他对象所引⽤的对象进⾏销毁。然后将伊甸园中的剩余对象移动到幸存 0区。若幸存 0区也满了,再对该区进⾏垃圾回收,然后移动到 1区。那如果1 区也满了呢?再移动到养⽼区。若养⽼区也满了,那么这个时候将产⽣MajorGC(FullGC)即重量垃圾回收,进⾏养⽼区的内存清理。若养⽼区执⾏了FullGC之后发现依然⽆法进⾏对象的保存,就会产⽣OOM异常“OutOfMemoryError”。

OutOfMemoryError

如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。原因有⼆:

  • Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。
  • 代码中创建了⼤量⼤对象,并且⻓时间不能被垃圾收集器收集(存在被引⽤)。

永久区

永久存储区是⼀个常驻内存区域,⽤于存放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在元空间

在这里插入图片描述

实际⽽⾔,⽅法区(Method Area)和堆⼀样,是各个线程共享的内存区域,它⽤于存储虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码等等,虽然JVM规范将⽅法区描述为堆的⼀个逻辑部分,但它却还有⼀个别名叫做Non-Heap(⾮堆),⽬的就是要和堆分开。

对于HotSpot虚拟机,很多开发者习惯将⽅法区称之为“永久代(Parmanent Gen)” ,但严格本质上说两者不同,或者说使⽤永久代来实现⽅法区⽽已,永久代是⽅法区(相当于是⼀个接⼝interface)的⼀个实现,jdk1.7的版本中,已经将原本放在永久代的字符串常量池移⾛。

直⽩⼀点其实就是⽅法区就是永久带的⼀种落地实现

常量池(Constant Pool)是⽅法区的⼀部分,Class⽂件除了有类的版本、字段、⽅法、接⼝等描述信息外,还有⼀项信息就是常量池,这部分内容将在类加载后进⼊⽅法区的运⾏时常量池中存放。

在这里插入图片描述

堆内存调优

在这里插入图片描述

Java8之后将最初的永久代取消了,由元空间取代。

在这里插入图片描述

Java调优内存量计算

在这里插入图片描述

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

VM参数

  • -Xms1024m : 最⼤内存
  • -Xmx1024m : 初始化内存
  • -XX:+printGCDetails : 打印内存⽇志信息

垃圾回收机制GC

GC是什么

Garbage Collection, 简称GC, 是分代垃圾收集算法。 频繁收集Yong区, 较少收集Old区, 基本不动Perm区。

在这里插入图片描述

ava 虚拟机中的内存总量。
System.out.println(“MAX_MEMORY = " + maxMemory + “(字节)、” + (maxMemory / (double)1024 / 1024) + “MB”);
System.out.println(“TOTAL_MEMORY = " + totalMemory +”(字节)、” + (totalMemory / (double)1024 / 1024) + “MB”);
}
}


**VM参数**

- -Xms1024m : 最⼤内存
- -Xmx1024m : 初始化内存
- -XX:+printGCDetails : 打印内存⽇志信息

## 垃圾回收机制GC

**GC是什么**

Garbage Collection, 简称GC, 是分代垃圾收集算法。 频繁收集Yong区, 较少收集Old区, 基本不动Perm区。

[外链图片转存中...(img-HVQbwXmz-1628926917612)]

JVM在进⾏GC时,并⾮每次都对上⾯三个内存区域⼀起回收的,⼤部分时候回收的都是指新⽣代。 因此GC按照回收的区域⼜分了两种类型,⼀种是普通GC(minorGC),⼀种是全局GC(major GC or Full GC), 普通GC(minor GC):只针对新⽣代区域的GC。 全局GC(major GC or Full GC):针对年⽼代的GC,偶尔伴随对新⽣代的GC以及对永久代的GC。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值