Skip to content

Commit

Permalink
feat(post): 完善文章
Browse files Browse the repository at this point in the history
  • Loading branch information
李济芝 committed Jun 26, 2024
1 parent 714e51c commit 8ef727a
Show file tree
Hide file tree
Showing 28 changed files with 1,234 additions and 1,279 deletions.
4 changes: 1 addition & 3 deletions blog-site/content/posts/java/Java数据类型.md
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,6 @@ public class MainTest {
如果一个`String` 对象已经被创建过了,那么就会从`String`常量池中取得引用。只有`String`是不可变的,才可能使用 字符串常量池。
4. 可以缓存`hash`值。因为`String``hash`值经常被使用,像`Set``Map`结构中的`key`值也需要用到`HashCode`来保证唯一性和一致性,因此不可变的`HashCode`才是安全可靠的。


### new String("abc")会创建几个对象
`new String("abc")`会创建几个对象? 这是面试中经常问的一个问题,先说答案两个字符串对象,前提是`String`常量池中还没有 "abc" 字符串对象。
第一个对象是"abc",它属于字符串字面量,因此编译时期会在字符串常量池中创建一个字符串对象,指向这个 "abc" 字符串字面量,而使用`new`的方式会在堆中创建一个字符串对象。
Expand Down Expand Up @@ -716,7 +715,7 @@ Constant pool:
而我们开发过程中,使用频率比较高,会有大量的字符串被创建,如果无法及时回收,容易导致永久代内存不足。所以JDK7之后将字符串常量池放到堆里,能及时回收内存,避免出现`OOM`错误。

如何证明Java8中的字符串常量池方到了堆中?代码演示:
```
```java
public class MainTest {
// 虚拟机参数: -Xmx6m -Xms6m -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
public static void main(String[] args) {
Expand Down Expand Up @@ -763,7 +762,6 @@ JDK1.7起,将这个字符串对象尝试放入字符串常量池:

![JDK78字符串常量池案例解析](/iblog/posts/annex/images/essays/JDK78字符串常量池案例解析.png)


#### intern方法
在Java中,字符串常量池是由JVM自动管理的,开发者通常不需要显式地将字符串加入到字符串常量池中。
Java中的字符串常量池有以下几种方式来存储字符串:
Expand Down
40 changes: 15 additions & 25 deletions blog-site/content/posts/jvm/JVM中的垃圾回收机制.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ pBridge->Register(kDestroy);
在堆中判定新生代中的幸存者区是否可以进老年代,会有一个年龄计数器,这里用的就是引用计数算法。

这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间循环引用的问题。

当p的指针断开的时候,内部的引用形成一个循环,从而造成内存泄漏。

![循环引用](/iblog/posts/annex/images/essays/循环引用.png)
Expand All @@ -100,30 +99,28 @@ pBridge->Register(kDestroy);
- 使用弱引用`weakref``weakref`是Python提供的标准库,旨在解决循环引用。

### 可达性分析算法
可达性分析算法是以根对象集合GCRoots为起始点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。
可达性分析算法是以根对象集合`GCRoots`为起始点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到`GCRoots`没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。

![可达性分析算法](/iblog/posts/annex/images/essays/可达性分析算法.png)

相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是,该算法可以有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。
只要你无法与`GC Root`建立直接或间接的连接,系统就会判定你为可回收对象。所谓根集合`GCRoots`就是一组必须活跃的引用,即有在栈中有指针指向堆中的地址,它们是程序运行时的起点,是一切引用链的源头。
只要你无法与`GCRoot`建立直接或间接的连接,系统就会判定你为可回收对象。所谓根集合`GCRoots`就是一组必须活跃的引用,即有在栈中有指针指向堆中的地址,它们是程序运行时的起点,是一切引用链的源头。

在 Java 中,`GC Roots`包括以下几种:
1. 虚拟机栈中引用的对象;例如:各个线程被调用的方法中使用到的参数、局部变量等;
2. 本地方法栈内本地方法引用对象方法区中类静态属性引用的对象;例如:Java类的引用类型静态变量;
3. 方法区中常量引用的对象;例如:字符串常量池里的引用;
4. 所有被同步锁`synchronized`持有的对象;
5. Java虚拟机内部的引用;例如:一些常驻的异常对象、系统类加载器等;
6. 反映java虚拟机内部情况的`JMXBean、JVMTI`中注册的回调、本地代码缓存等;
7. 除了这些固定的`GC Roots`集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完整`GC Roots`集合,比如:分代收集和局部回收
在Java中,`GCRoots`包括以下几种:
1. 虚拟机栈中引用的对象;例如:各个线程被调用的方法中使用到的参数、局部变量等;
2. 本地方法栈内,本地方法引用对象方法区中类静态属性引用的对象;例如:Java类的引用类型静态变量;
3. 方法区中常量引用的对象;例如:字符串常量池里的引用;
4. 所有被同步锁`synchronized`持有的对象;
5. Java虚拟机内部的引用;例如:一些常驻的异常对象、系统类加载器等;
6. 反映Java虚拟机内部情况的`JMXBean、JVMTI`中注册的回调、本地代码缓存等;
7. 除了这些固定的`GC Roots`集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完整`GCRoots`集合,比如:分代收集和局部回收

除了堆空间产生对象的一些结构外,比如:虚拟机栈、本地方法栈、方法区、字符串常量池等地方对堆空间的对象的引用,都可以作为GC Roots进行可达性分析
除了堆空间产生对象的一些结构外,比如:虚拟机栈、本地方法栈、方法区、字符串常量池等地方对堆空间的对象的引用,都可以作为`GCRoots`进行可达性分析

![对象集合GCroots](/iblog/posts/annex/images/essays/对象集合GCroots.png)

如何判定是否为`GC root`?
由于Root采用栈方式存放变量和指针,所以如果一个指针,保存了堆内存里面的对象,但是自己又不存放在堆内存里面,那它就是一个`GC root`

举例:
由于Root采用栈方式存放变量和指针,所以如果一个指针,保存了堆内存里面的对象,但是自己又不存放在堆内存里面,那它就是一个`GC root`。代码演示:
```java
public class StackReference {
public void greet() {
Expand All @@ -136,16 +133,9 @@ public class StackReference {
}
}
```
- 在 greet 方法中,localVar 是一个局部变量,存在于虚拟机栈中,可以被认为是 GC Roots。
- 在 greet 方法执行期间,localVar 引用的对象是活跃的,因为它是从 GC Roots 可达的。
- 当 greet 方法执行完毕后,localVar 的作用域结束,localVar 引用的 Object 对象不再由任何 GC Roots 引用(假设没有其他引用指向这个对象),因此它将有资格作为垃圾被回收掉

对象可以被回收,就代表一定会被回收吗?

即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;
可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 `finalize` 方法。
当对象没有覆盖 `finalize` 方法,或 `finalize` 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。
-`greet`方法中`localVar`是一个局部变量,存在于虚拟机栈中,可以被认为是`GCRoots`
-`greet`方法执行期间,`localVar`引用的对象是活跃的,因为它是从`GCRoots`可达的。
-`greet`方法执行完毕后,`localVar`的作用域结束,`localVar`引用的 `Object` 对象不再由任何`GCRoots`引用(假设没有其他引用指向这个对象),因此它将有资格作为垃圾被回收掉。

如果要使用可达性分析算法来判断内存是否可回收,那么分析工作必须在一个能保障一致性的快照中进行,如果这点不满足,分析结果的准确性就无法保证。
简单来说就是执行这个算法的时候,要停止程序标记对象,不能一边改变对象的引用一边判定对象是不是垃圾。
Expand Down
9 changes: 2 additions & 7 deletions blog-site/content/posts/jvm/JVM中的方法区.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@ public class content.posts.jvm.MainTest

### 域信息
JVM必须在方法区中,保存类型的所有域的相关信息以及域的声明顺序。

域的相关信息包括:域名称、域类型、域修饰符(public,private,protected,static,final,volatile,transient的某个子集)。
```
// ...
Expand Down Expand Up @@ -213,10 +212,9 @@ class Test {
}
}
```

全局常量就是使用`static final`进行修饰,被声明为`final`的类变量的处理方法则不同,每个全局常量在编译的时候就会被分配了。

### [运行时常量池](/iblog/posts/java/rookie-datatype/#常量池)
### 运行时常量池
运行时常量池是每一个类或接口的常量池的运行时表示形式。

它包括了若干种不同的常量:从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。
Expand All @@ -230,7 +228,7 @@ class Test {
我们常说的常量池,就是指方法区中的运行时常量池。

运行时常量池类似于传统编程语言中的符号表,但是它所包含的数据却比符号表要更加丰富一些。
当创建类或接口的运行时常量池时,如果构造运行时常量池所需的内存空间超过了方法区所能提供的最大值,则JVM会抛outOfMemoryError异常
当创建类或接口的运行时常量池时,如果构造运行时常量池所需的内存空间超过了方法区所能提供的最大值,则JVM会抛`OutOfMemoryError`异常

## 字节码指令解析
演示代码如下
Expand Down Expand Up @@ -359,6 +357,3 @@ Java虚拟机被允许对满足上述三个条件的无用类进行回收,这
在计算机科学中,字面量(literal)是用于表达源代码中一个固定值的表示法(notation)。
几乎所有计算机编程语言都具有对基本值的字面量表示,诸如:整数、浮点数以及字符串;
而有很多也对布尔类型和字符类型的值也支持字面量表示;还有一些甚至对枚举类型的元素以及像数组、记录和对象等复合类型的值也支持字面量表示法。



23 changes: 8 additions & 15 deletions blog-site/content/posts/jvm/JVM中的直接内存.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ slug: "jvm-direct-memory"
---

直接内存不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。直接内存是在Java堆外的、直接向系统申请的内存区间。

操作直接内存演示代码:
```
而且也可能导致`OutOfMemoryError`错误出现。操作直接内存演示代码:
```java
public class MainTest {
public static void main(String[] args) {
ByteBuffer allocate = ByteBuffer.allocate(1024 * 1024 * 1024);
Expand All @@ -33,10 +32,8 @@ public class MainTest {

![NIO读写](/iblog/posts/annex/images/essays/NIO读写.png)

通常,访问直接内存的速度会优于Java堆。即读写性能高。因此出于性能考虑,读写频繁的场合可能会考虑使用直接内存。Java的NIO库允许Java程序使用直接内存,用于数据缓冲区。

直接内存也存在 `OutOfMemoryError` 异常:`OutOfMemoryError: Direct buffer memory`
```
通常,访问直接内存的速度会优于Java堆,即读写性能高。因此出于性能考虑,读写频繁的场合可能会考虑使用直接内存。Java的NIO库允许Java程序使用直接内存,用于数据缓冲区。
```java
public class MainTest {
private static final int BUFFER = 1024 * 1024 * 20;
public static void main(String[] args) {
Expand All @@ -59,11 +56,7 @@ public class MainTest {
}
}
```

由于直接内存在Java堆外,因此它的大小不会直接受限于`-Xmx`指定的最大堆大小,但是系统内存是有限的,Java堆和直接内存的总和依然受限于操作系统能给出的最大内存。

直接内存缺点:
- 分配回收成本较高
- 不受JVM内存回收管理

直接内存大小可以通过 `MaxDirectMemorySize` 设置,如果不指定,默认与堆的最大值`-Xmx`参数值一致。
由于直接内存在Java堆外,因此它的大小不会直接受限于`-Xmx`指定的最大堆大小。
直接内存大小可以通过 `MaxDirectMemorySize` 设置,如果不指定,默认与堆的最大值`-Xmx`参数值一致。
系统内存是有限的,Java堆和直接内存的总和依然受限于操作系统能给出的最大内存。
所以直接内存也有一些缺点,分配回收成本较高和不受JVM内存回收管理。
Loading

0 comments on commit 8ef727a

Please sign in to comment.