常量池的类型有3种
字面量进入字符串常量池的时机:在类加载时字面量会进入运行时常量池中,但不会进入到全局字符串常量池中,也不会在堆中产生相应的对象.
intern方法的作用是当一个字符串的字面量引用存在于字符串常量池时,就返回字符串常量池中的该引用;当不存在时会会在字符串常量池创建一个引用指向该字符串并返回该引用.
"==“比较的是字符串的地址而非字符串的值.常见的”=="混淆场景
3. 字面量和字符串引用.
```java
String s1 = "Java";
String s2 = new String("java");
System.out.println(s1 == s2); // false
```
s1指向字面量"Java",而字面量引用是直接存储在字符串常量池中,所以s1指向字符串常量池中的引用;而s2首先先在堆中创建一个对象指向字符串的引用,然后s2指向堆上的对象引用.所以s1和s2指向的不是同一个地址.![在这里插入图片描述](https://img-blog.csdnimg.cn/bbf8dc56467b47bda29eb60c92546828.png)
连接得到的字符串和字面量
String s1 = "Java";
String s2 = "Ja" + "va";
String s3 = new String("Java");
String s4 = new String("Ja") + "va";
String s5 = new String("Ja") + new String("va");
String s6 = new String("J") + new String("ava");
System.out.println(s1 == s2); //true
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false
System.out.println(s1 == s5);//false
System.out.println(s3 == s5);//false
System.out.println(s1 == s6);//false
System.out.println(s3 == s6);//false
由字面量连接得到的字符串仍是一个字面量,所以也会直接指向字符串常量池中的引用;而一旦涉及到在堆上创建新的对象,则新的对象的引用一定是不同的.
字符串和字符串的intern
String s1 = "Java";
String s2 = s1.intern();
String s3 = new String("Java");
String s4 = s3.intern();
System.out.println(s1 == s2); // true
System.out.println(s3 == s4); // false
System.out.println(s1 == s4); // true
首先s1指向字符串常量池中的字面量引用.
s2指向s1.intern,由于字符串常量池中存在s1字面量的引用,所以s2也直接指向字符串常量池中的引用.
s3在指向的时候会先查看"Java"在字符串常量池中是否存在,如果没有存在则会创建一个字符串引用.然后再在堆上创建对象.
s4指向s3.intern,由于s3的字面量和s1的字面量相同,都是Java,所以s3的字面量引用也存在于字符串常量池中,所以s4指向字符串常量池中的引用,所以s1,s2,s4都是指向字符串常量池中的字面量引用,而s3指向堆上新创建的对象
String s1 = new String("C") + new String("++");
System.out.println(s1 == s1.intern()); //true
字符串拼接并不会产生"C++"这样的字面量,所以在创建s1时不会在字符串常量池中产生相关字面量的应用.所以s1,intern后字符串常量池中的引用就是s1指向的堆上的引用.
注意:在字符串拼接过程中,并不是所有字符串都是如此,诸如"Java"以及一些基本数据类型的字符串会在编译器就将字面量intern进字符串常量池中.
String s1 = new String("Ja") + new String("va");
System.out.println(s1 == s1.intern()); //false
首先在Java中,实例化对象是非常常见的.诸如Demo demo = new Demo()
就是一个对象实例化.在这个语句中,可以被分成四部分,首先new Demo
是创建一个Demo对象,创建对象时需要在堆上为对象分配内存空间,同时需要加载Demo这个类.类加载的过程分为加载,验证,准备,解析,初始化;之后new Demo()
中的()则是执行Demo类中的构造方法初始化Demo类中的字段并执行实例代码块.对于一个子类而言,构造方法的执行顺序:父类构造器优先于子类构造器执行;然后Demo demo
则是在栈上创建一个名为demo的对象引用;最后的=
则是将对象引用指向堆上的对象实例.(类加载的详细过程参考[类加载模型])
GC回收机制是在程序中当对象调用结束后不再使用后会变成垃圾占用内存.为了释放内存给其他资源使用,所以Java中有GC回收机制.
GC回收算法一共有4中算法,分别是标记-清除,复制算法,标记-整理,分代算法,
类加载的整个过程分为加载,连接(验证,准备,解析),初始化,
从Java虚拟机方面看,类加载器可以被分为两种:一种是启动类加载器(BoostrapClassLoader),是虚拟机自身的一部分;另一种就是其他的类加载器,独立存在于虚拟机外部.
而从开发者角度,为了更细致的进行类加载,保留了三层类加载器,双亲委派的类加载架构器.
双亲委派模型由3部分组成:启动类加载器(BootstrapClassLoader),标准扩展类加载器(ExtensionClassLoader)和应用类加载器(ApplicationClassLoader)以及自定义类加载器(UserClassLoader).其中启动类加载器负责加载JDK中lib目录下的核心类库,即加载标准库中的类;标准扩展类负责加载jdk目录中扩展的类;应用类加载器负责加载当前项目中的类,自定义类加载器负责加载指定路径下的类.加载的等级依次降低.而双亲委派模型则是按照加载等级从高到低加载,当父加载器加载完后仍无法加载到需要的类,才会在本加载器中查找加载.
双亲委派模型类加载过程:
双亲委派模型的优点
尽管双亲委派模型有很多优点,但在一些场景下也存在一定的问题,如Java中SPI(Server Provider Interface)机制下的JDBC实现
JDBC中的DriveManger类中的实现类loadInitialDrivers()是由线程上下文加载器加载的(属于ApplicationClassLoader),并没有向上去委派其父类类加载器,造成这种现象的原因是其父类类加载器只能加载指定路径下的类,而该实现类是由开发者实现的,其父类类加载器加载不到.
双亲委派模型的细节参考我竟然被“双亲委派”给虐了
内存溢出是指程序中使用的内存超过了系统可分配的最大内存而发生了out of memory.
内存泄露是当某个对象使用完成后,本应该被回收的内存因为某些原因而没有被回收.这种现象就是内存泄漏.
当应用系统中存在大量内存泄露或占用内存过多就会导致内存溢出.
内存泄漏的场景:
static字段引起的内存泄漏
static字段拥有和程序相匹配的生命周期.当程序中大量使用static字段时,容易引发内存泄漏
public class Test{
static List<Integer> list = new ArrayList<>();
public void func(){
for(int i = 0; i < 100; i++){
list.add(i);
}
}
public static void main(String[] args){
new Test().func();
}
}
解决方法是少使用static字段以及使用懒汉模式初始化静态字段
资源未关闭而导致内存泄漏,如数据库连接后没有调用close()函数,调用ReetrantLock后没有调用lock()函数关闭锁
解决方法是程序中使用finally块关闭资源.
hashcode数据结构产生的内存泄漏:
ThreadLocal使用结束后没有使用remove函数造成的内存泄漏.
ThreadLocal中使用一个弱引用map,当ThreadLocal中的强引用被回收后,map里的value没有被回收掉但却不会再使用,因此造成了内存泄露.尤其是Threadlocal实例被置位null,更容易引发内存泄露.
解决方法:使用完ThreadLocal后使用remove函数
监视器未释放
web开发中经常会用到监视器(Listener),但在释放对象的时候却没有去删除这些监听器,增加了内存泄漏的机会。
内部类和外部模块的引用
在非静态内部类和匿名内部类中,通常会隐含引用其包装类.当在程序中使用内部类时,即使包装类被回收,内部类也不会被回收.
解决方法是尽可能多的使用静态内部类而非非静态内部类.
1:2
JMM是JVM定义的一套内存模型,是为了解决不同操作系统的内存访问的差异性.
JMM的主要目标是定义程序中各个变量的访问规则,即在JVM中将变量存储到内存中和从内存中读取变量.此处的变量包括实例字段,静态字段和构成数组对象的元素,不包括局部变量和形参.
JMM规定了所有变量都存储到主内存中,每条线程拥有自己的工作内存,工作内存中保存了该线程使用的变量的主内存的拷贝,线程对变量的操作在工作内存中完成.不同的线程不能直接访问其他线程工作内存中的变量,而需要主内存去传递对应的变量值.
为了实现主内存和工作内存之间的交互,JMM定义了8种操作.且保证这8种操作都是原子的,不可分的.
因此,并发地执行程序,只有同时保证原子性,可见性和有序性才能保证程序正常运行,三者缺一不可.