Java 基础学习第一弹

Java 堆主要分为 2 个区域-年轻代与老年代,其中年轻代又分 Eden 区和 Survivor 区,其中 Survivor 区又分 From 和 To 2 个区。可能这时候大家会有疑问,为什么需要 Survivor 区,为什么 Survivor 还要分 2 个区。

大多数情况下,对象会在新生代 Eden 区中进行分配。当 Eden 区没有足够空间进行分配时,虚拟机会发起一次 Minor GC,Minor GC 相比 Major GC 更频繁,回收速度也更快。

通过 Minor GC 之后,Eden 会被清空,Eden 区中绝大部分对象会被回收,而那些无需回收的存活对象,将会进到 Survivor 的 From 区(若 From 区不够,则直接进入 Old 区)。

Survivor 区相当于是 Eden 区和 Old 区的一个缓冲,类似于我们交通灯中的黄灯。Survivor 又分为 2 个区,一个是 From 区,一个是 To 区。每次执行 Minor GC,会将 Eden 区和 From 存活的对象放到 Survivor 的 To 区(如果 To 区不够,则直接进入 Old 区)。

之所以有 Survivor 区是因为如果没有 Survivor 区,Eden 区每进行一次 Minor GC,存活的对象就会被送到老年代,老年代很快就会被填满。而有很多对象虽然一次 Minor GC 没有消灭,但其实也并不会蹦跶多久,或许第二次,第三次就需要被清除。这时候移入老年区,很明显不是一个明智的决定。

所以,Survivor 的存在意义就是减少被送到老年代的对象,进而减少 Major GC 的发生。Survivor 的预筛选保证,只有经历 16 次 Minor GC 还能在新生代中存活的对象,才会被送到老年代。

设置两个 Survivor 区最大的好处就是解决内存碎片化。

我们先假设一下,Survivor 如果只有一个区域会怎样。Minor GC 执行后,Eden 区被清空了,存活的对象放到了 Survivor 区,而之前 Survivor 区中的对象,可能也有一些是需要被清除的。问题来了,这时候我们怎么清除它们?在这种场景下,我们只能标记清除,而我们知道标记清除最大的问题就是内存碎片,在新生代这种经常会消亡的区域,采用标记清除必然会让内存产生严重的碎片化。因为 Survivor 有 2 个区域,所以每次 Minor GC,会将之前 Eden 区和 From 区中的存活对象复制到 To 区域。第二次 Minor GC 时,From 与 To 职责互换,这时候会将 Eden 区和 To 区中的存活对象再复制到 From 区域,以此反复。

这种机制最大的好处就是,整个过程中,永远有一个 Survivor space 是空的,另一个非空的 Survivor space 是无碎片的。那么,Survivor 为什么不分更多块呢?比方说分成三个、四个、五个?显然,如果 Survivor 区再细分下去,每一块的空间就会比较小,容易导致 Survivor 区满,两块 Survivor 区可能是经过权衡之后的最佳方案。

老年代占据着 2/3 的堆内存空间,只有在 Major GC 的时候才会进行清理,每次 GC 都会触发“Stop-The-World”。内存越大,STW 的时间也越长,所以内存也不仅仅是越大就越好。在内存担保机制下,无法安置的对象会直接进到老年代,以下几种情况也会进入老年代。

1)大对象,指需要大量连续内存空间的对象,这部分对象不管是不是“朝生夕死”,都会直接进到老年代。这样做主要是为了避免在 Eden 区及 2 个 Survivor 区之间发生大量的内存复制。

2)长期存活对象,虚拟机给每个对象定义了一个对象年龄(Age)计数器。正常情况下对象会不断的在 Survivor 的 From 区与 To 区之间移动,对象在 Survivor 区中每经历一次 Minor GC,年龄就增加 1 岁。当年龄增加到 15 岁时,这时候就会被转移到老年代。当然,这里的 15,JVM 也支持进行特殊设置。

3)动态对象年龄,虚拟机并不重视要求对象年龄必须到 15 岁,才会放入老年区,如果 Survivor 空间中相同年龄所有对象大小的总合大于 Survivor 空间的一半,年龄大于等于该年龄的对象就可以直接进去老年区,无需等你“成年”。

这其实有点类似于负载均衡,轮询是负载均衡的一种,保证每台机器都分得同样的请求。看似很均衡,但每台机的硬件不通,健康状况不同,我们还可以基于每台机接受的请求数,或每台机的响应时间等,来调整我们的负载均衡算法。

3. String、StringBuffer、StringBuilder的区别

在Java中,StringStringBufferStringBuilder都是用于处理字符串的类,但它们在性能、线程安全性和可变性方面存在一些区别。

String(字符串)String是Java中最常用的字符串类,它是不可变的(immutable)。这意味着一旦创建了一个String对象,它的值就不能被修改。每次对String的操作(例如连接、替换等)都会创建一个新的String对象。这种不可变性使得String具有线程安全性,适合在多线程环境下使用。然而,频繁的字符串操作可能会导致内存开销较大,因为每次操作都会创建新的对象。

1 2 3String``str = "Hello"``; str +``= " World"``;``/``/ 创建了一个新的String对象 `````

StringBuffer(字符串缓冲区)StringBuffer是可变的(mutable)字符串类,它可以进行多次修改而无需创建新的对象。StringBuffer是线程安全的,适用于多线程环境下的字符串操作。它提供了多个方法用于对字符串进行修改、连接、插入和删除等操作。

1 2 3 4 5StringBuffer sb``= new StringBuffer(``"Hello"``); sb.append(``" World"``);``/``/ 在原对象上进行修改,无需创建新对象 ``````由于StringBuffer是线程安全的,它的执行速度相对较慢。因此,如果在单线程环境下进行字符串操作,推荐使用StringBuilder,因为它的执行速度更快。`

StringBuilder(字符串构建器)StringBuilder也是可变的字符串类,类似于StringBuffer,它可以进行多次修改而无需创建新的对象。StringBuilder不是线程安全的,因此在多线程环境下使用时需要进行外部同步。由于不需要额外的线程安全检查,StringBuilder的执行速度相对较快。

1 2 3StringBuilder sb``= new StringBuilder(``"Hello"``); sb.append(``" World"``);``/``/ 在原对象上进行修改,无需创建新对象 `````

总结:

  • 如果需要频繁操作字符串,并且在多线程环境下使用,应该使用StringBuffer
  • 如果需要频繁操作字符串,但在单线程环境下使用,应该使用StringBuilder,因为它的执行速度更快。
  • 如果不需要频繁操作字符串,或者字符串是不可变的,可以使用String
4. 操作字符串常见的类及方法

String类:String是Java中最常用的字符串类,它提供了许多方法来处理字符串。以下是一些示例:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27String str1``= "Hello"``; String str2``= "World"``; /``/ 连接字符串 String result1``= str1``+ str2;``/``/ 结果为``"HelloWorld" /``/ 获取字符串长度 int length``= str1.length();``/``/ 结果为``5 /``/ 检查字符串是否为空 boolean isEmpty``= str1.isEmpty();``/``/ 结果为 false /``/ 检查字符串是否包含指定字符 boolean contains``= str1.contains(``"H"``);``/``/ 结果为 true /``/ 提取子字符串 String subStr``= str1.substring(``1``,``4``);``/``/ 结果为``"ell" /``/ 替换字符 String replacedStr``= str1.replace(``"H"``,``"J"``);``/``/ 结果为``"Jello" /``/ 拆分字符串 String[] parts``= str1.split(``"l"``);``/``/ 结果为 [``"He"``, "``", "``o"] /``/ 转换为大写或小写 String upperCase``= str1.toUpperCase();``/``/ 结果为``"HELLO" String lowerCase``= str1.toLowerCase();``/``/ 结果为``"hello"

StringBuilder类:StringBuilder用于构建可变字符串,它提供了一系列方法来进行字符串的拼接和修改。以下是一些示例:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19StringBuilder sb``= new StringBuilder(); /``/ 追加字符串 sb.append(``"Hello"``); sb.append(``"World"``); /``/ 插入字符串 sb.insert(``5``,``" "``); /``/ 替换字符串 sb.replace(``6``,``11``,``"Java"``); /``/ 删除字符 sb.deleteCharAt(``5``); /``/ 反转字符串 sb.reverse(); String result2``= sb.toString();``/``/ 结果为``"avaJdlroW"

StringBuffer类:StringBufferStringBuilder类似,也用于构建可变字符串。不同的是,StringBuffer是线程安全的,适用于多线程环境下的字符串操作。以下是一个示例:

1 2 3 4 5 6StringBuffer``buffer = new StringBuffer(); buffer``.append(``"Hello"``); buffer``.append(``"World"``); String result3``= buffer``.toString();``/``/ 结果为``"HelloWorld"
5. Static的用法和作用

static 是Java中的一个关键字,可以应用于变量、方法和代码块。它具有以下几种用法和作用:

静态变量(Static Variables):使用 static 关键字声明的变量称为静态变量,也称为类变量。静态变量属于类而不是实例,它在类加载时被初始化,并且在整个程序执行期间保持不变。静态变量可以通过类名直接访问,无需创建类的实例。静态变量常用于表示在类的所有实例之间共享的数据。

1 2 3 4 5 6 7public``class MyClass { static``int count``= 0``;``/``/ 静态变量 public MyClass() { count``+``+``;``/``/ 每次创建实例时,静态变量 count 自增 } }

静态方法(Static Methods):使用 static 关键字声明的方法称为静态方法。静态方法属于类而不是实例,它可以在类加载时直接调用,无需创建类的实例。静态方法只能访问静态变量和调用其他静态方法,不能直接访问实例变量或调用实例方法。

1 2 3 4 5public``class MathUtils { public static``int add(``int a,``int b) {``/``/ 静态方法 return a``+ b; } }

静态代码块(Static Initialization Blocks):静态代码块用于在类加载时执行一些初始化操作。它使用 static 关键字定义,并用花括号括起来的代码块。静态代码块只执行一次,且在类的第一次使用时执行。

| | |
 b; } }` |

静态代码块(Static Initialization Blocks):静态代码块用于在类加载时执行一些初始化操作。它使用 static 关键字定义,并用花括号括起来的代码块。静态代码块只执行一次,且在类的第一次使用时执行。

| | |

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值