牛客网笔记
protected
protected在同一包或子类中使用,无修饰符只能被同一包中的类访问
count++
package cn.chen.base;
public class Demo {
public static void main(String[] args) {
int count = 0;
int num = 0;
for(int i = 0;i <= 100;i ++){
num = num + i;
count = count++;
}
System.out.println(num * count);
count = 0;
num = 0;
for(int i = 0;i <= 100;i ++){
num = num + i;
count++;
}
System.out.println(num * count);
count = 0;
num = 0;
for(int i = 0;i <= 100;i ++){
num = num + i;
count = ++count;
}
System.out.println(num * count);
}
}
0
510050
510050Process finished with exit code 0
C++中count = count++等效于count++,但是在Java中:首先将count的值(不是引用)存储在一个临时变量区,然后对count进行加1操作,最后返回临时变量区的值。
count = count++ 可以这么理解:
public static int mockAdd(int count){
int temp = count;
count = count + 1;
return temp;
}
字符串常量池和JVM运行时的数据区
字符串常量池:JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被称为字符串常量池或者字符串字面量池。
工作原理:当代码出现字面量形式创建字符串对象时,JVM首先对这个字面量进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回,否则新的字符串对象被创建,然后将这个引用放入字符串常量池,并返回该引用。
实现前提:String对象是不可变的,可以保证多个对象共享同一对象。
如果用new来构造字符串对象,不管字符串常量池中有没有相同内容的对象引用,新的字符串对象都会被创建。
使用new创建的字符串对象,如果想将这个对象的引用加入到字符串常量池,可以使用intern方法。调用intern后,首先检查字符串常量池中是否有该对象的引用,如果存在,则将这个引用返回给变量,否则将引用加入并返回给变量。
字符串常量池中存放的是对象引用,不是对象。在Java中,对象都创建在堆内存中。
字符串常量池存在于堆内存中的永久代。
Java中唯一重载的运算符就是字符串拼接相关的+,+=
。
既然对象不可变,那么问题来了:如果多个字符串拼接是不是会产生中间额外的String对象?
class Test{
public static void main(String[] args) {
String username = "chen";
String age = "21";
String job = "Developer";
String info = username + age + job;
System.out.println(info);
}
}
编译器的优化处理:
javap反编译一下
➜ Desktop git:(master) ✗ javap -c Test
Compiled from "Test.java"
class Test {
Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String chen
2: astore_1
3: ldc #3 // String 21
5: astore_2
6: ldc #4 // String Developer
8: astore_3
9: new #5 // class java/lang/StringBuilder
12: dup
13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
16: aload_1
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: aload_3
25: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore 4
33: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
36: aload 4
38: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
41: return
}
可见上面有StringBuilder对象,使用append方法就行拼接,就不会产生中间String对象了。
问题2:仅靠编译器优化就够了吗?
class Test{
public void testStringBuilder(String[] values){
String result = "";
for(String v : values){
result += v;
}
System.out.println(result);
}
}
javap反编译一下
➜ Desktop git:(master) ✗ javap -c Test
Compiled from "Test.java"
class Test {
Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void testStringBuilder(java.lang.String[]);
Code:
0: ldc #2 // String
2: astore_2
3: aload_1
4: astore_3
5: aload_3
6: arraylength
7: istore 4
9: iconst_0
10: istore 5
12: iload 5
14: iload 4
16: if_icmpge 51
19: aload_3
20: iload 5
22: aaload
23: astore 6
25: new #3 // class java/lang/StringBuilder
28: dup
29: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
32: aload_2
33: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: aload 6
38: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
41: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
44: astore_2
45: iinc 5, 1
48: goto 12
51: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
54: aload_2
55: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
58: return
}
我们看到在16: if_icmpge 51
和48: goto 12
,这里构成了一个循环。16: if_icmpge 51
意思是如果JVM操作数栈的for循环条件不成立,就跳到51行,48: goto 12
则表示跳到第5行。
这之间有一个问题就是StringBuilder对象的创建,也就是说在循环中重复创建了多个StringBuilder对象。我们优化一下:
class Test{
public void testStringBuilder(String[] values){
StringBuilder result = new StringBuilder();
for(String v : values){
result.append(v);
}
System.out.println(result);
}
}
我们再反编译一下:
➜ Desktop git:(master) ✗ javap -c Test
Compiled from "Test.java"
class Test {
Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void testStringBuilder(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: astore_2
8: aload_1
9: astore_3
10: aload_3
11: arraylength
12: istore 4
14: iconst_0
15: istore 5
17: iload 5
19: iload 4
21: if_icmpge 43
24: aload_3
25: iload 5
27: aaload
28: astore 6
30: aload_2
31: aload 6
33: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: pop
37: iinc 5, 1
40: goto 17
43: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
46: aload_2
47: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
50: return
}
可以看到21: if_icmpge 43
和40: goto 17
构成循环,但是没有了刚才的问题。
堆和栈
查看堆的默认值
➜ Desktop git:(master) ✗ java -XX:+PrintFlagsFinal -version | grep HeapSize
uintx ErgoHeapSizeLimit = 0 {product}
uintx HeapSizePerGCThread = 87241520 {product}
uintx InitialHeapSize := 134217728 {product}
uintx LargePageHeapSizeThreshold = 134217728 {product}
uintx MaxHeapSize := 2147483648 {product}
java version "1.8.0_102"
Java(TM) SE Runtime Environment (build 1.8.0_102-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14, mixed mode)
查看栈的默认值
➜ Desktop git:(master) ✗ java -XX:+PrintFlagsFinal -version | grep ThreadStackSize
intx CompilerThreadStackSize = 0 {pd product}
intx ThreadStackSize = 1024 {pd product}
intx VMThreadStackSize = 1024 {pd product}
java version "1.8.0_102"
Java(TM) SE Runtime Environment (build 1.8.0_102-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14, mixed mode)
如果栈内存没有可用的可控存储方法调用和局部变量,JVM会抛出java.lang.StackOverFlowError
。如果堆内存没有可用的空间存储生成的对象,JVM会抛出java.lang.OutOfMemoryError
。
JVM运行时的数据区分类:
1. 程序计数器
2. JVM栈
3. 堆内存
4. 方法区
5. 运行时常量池
6. 本地方法栈
- 按线程持有划分:
- 单个线程私有:程序计数器、JVM栈和本地方法栈。每个线程私有,根据所属线程创建时初始化,随着线程结束被销毁。
- 多个线程共享:堆内存、方法区和运行时常量池。可以被每一个线程访问,随着JVM启动而初始化,随着JVM关闭而销毁。
- 程序计数器:
记录当前线程正在执行的指令。如果执行的是本地方法,则值为undefined。该区域是唯一不会抛出OutOfMemoryError的运行时数据区。 栈
- StackOverflowError 出现在栈内存设置成固定值的时候,当程序执行需要的栈内存超过设定的固定值。
- OutOfMemoryError 出现在栈内存设置为动态增长的时候,当JVM尝试申请的内存大小超过了可用内存。
栈帧:一个栈帧随着一个方法的调用开始而创建,这个方法调用完成之后销毁。栈帧内存放着方法中的局部变量、操作数栈等数据。
JVM栈只对栈帧进行存储、压栈和出栈操作。栈内存的大小可以有两种设置,固定值和根据线程需要动态增长。
堆
- 存放对象和数组(特殊的对象)。
- 由多个线程共享。
- 随着JVM的启动而创建。
- 垃圾回收就操作这个数据区来回收对象进而释放内存。
- 如果堆内存剩余的内存不足以满足于对象的创建,JVM就会抛出OutOFMemoryError。
方法区
- 在JVM规范中,方法区被视为堆内存的一个逻辑部分。这一点可能会由于具体的JVM实现而不同,甚至方法区不实现GC也是可以的。
- 方法区和堆内存一样被多个线程访问,方法区中存放类的信息:比如类加载器引用,属性,方法代码,构造方法和常量等。
- 当方法区的可用内存无法满足内存分配需求时,JVM会抛出OutOfMemoryError。
- 运行时常量池
- 运行时常量池创建在方法区,当一个类或一个接口被创建的时候,JVM会创建一个运行时常量池。
- 一个运行时常量池实际上是一个类或者接口的class文件中常量池表的运行时展示形式。
- 一个运行时常量池包含了多种类型的常量,从诸如运行时可以确定的数值型字面量到运行时才能确定的方法和属性引用。
- 当运行时常量池无法满足于内存分配需求时,JVM会抛出OutOfMemoryError。
- 本地方法栈
- 支持native方法调用的JVM实现。
- 基本和JVM栈一样。也有两种大小设置和两种错误。