java的并发采用“共享内存”模型,线程之间通过读写内存的公共状态进行通讯,多个线程之间是不能通过直接传递数据交互的,他们之间交互只能通过共享变量来实现。
==JMM的主要目的是定义程序中各个变量的访问规则。==java线程之间的通信由JMM控制。JMM定义了JVM在计算机内存(RAM)中的工作方式,如果想深入理解java并发编程,就要先理解好java内存模型。
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。此处的变量与Java编程中的变量不是完全等同的。这里的变量指的是实例字段、静态字段、构成数组对象的元素,但不包括局部变量与方法参数,因为它们是线程私有的,不会被共享,自然就不会存在竞争的问题。
Java内存模型定义了8种方法来完成主内存与工作内存之间的交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存。虚拟机实现的时候,必须每一种操作都是原子的、不可再分的。
lock
(锁定):作用于主内存
的变量,一个变量在同一时间只能一个线程锁定,该操作表示这条线成独占这个变量unlock
(解锁):作用于主内存
的变量,表示这个变量的状态由处于锁定状态被释放,这样其他线程才能对该变量进行锁定read
(读取):作用于主内存变量
,表示把一个主内存变量的值传输到线程的工作内存,以便随后的load操作使用。load
(载入):作用于线程的工作内存
的变量,表示把read操作从主内存中读取的变量的值放到工作内存的变量副本中(副本是相对于主内存的变量而言的)use
(使用):作用于线程的工作内存
中的变量,表示把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时就会执行该操作assign
(赋值):作用于线程的工作内存
的变量,表示把执行引擎返回的结果赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码指令时就会执行该操作store
(存储):作用于线程的工作内存
中的变量,把工作内存中的一个变量的值传递给主内存,以便随后的write操作使用write
(写入):作用于主内存
的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中如果要把一个变量从主内存传输到工作内存,那就要顺序的执行read和load操作,如果要把一个变量从工作内存回写到主内存,就要顺序的执行store和write操作。只需要保证相对顺序,不要求连续,下边两种执行结果是一样的:
在执行这8中操作的时候必须遵循如下的规则
JVM将内存分为5大区域,程序计数器、虚拟机栈、本地方法栈、java堆、方法区;
从字节码加载类经历三个阶段:
在程序获取引导类加载器或者String的类加载器,会发现得到结果为null,这是因为String属于Java的核心类,由引导类加载器加载,而引导类加载器是最高级的加载器,嵌套到JVM内部,无法在程序中获取。
Java虚拟机对class文件采用的是按需加载的方式,当需要使用该类时,才会将它的class文件加载到内存生成的class对象。而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即将请求交由给父类来处理,它是一种任务委派模式。
双亲委派的优势在于:
在JVM中表示两个class对象是否为同一个类存在两个必要条件:
换句话说,在JVM中,即使这两个类对象(class对象)来源同一个class文件,被同一个虚拟机所加载,但只要加载它们的ClassLoader实例对象不同,那么这两个类对象也是不相等的。
JVM必须知道一个类型是由启加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解析一个类型到另一个类型的引用的时候,JVM需要保证这两个类型的类加载器是相同的。
大部分拉丁字符都可以用一个byte表示,用两个字节的char,浪费了空间,但是类似汉字字符,还是必须用两个字节表示,即2个byte,那么使用一个byte就可以满足需求的,可以使用编码标记(
end-flag
),表示已经编码结束。
String a1 = "aaa";
)会直接放入字符串常量池,返回地址,而使用new String("aaa");
构造的String,会在堆区新建"aaa"的对象返回,同时入参"aaa"会被存入字符串常量池。String a1 = "aaa";
String a2 = new String("aaa");
String a3 = "aaa";
String a4 = new String("aaa");
System.out.println(a1 == a2); // false
System.out.println(a2 == a3); // false
System.out.println(a1 == a3); // true
System.out.println(a2 == a4); // false
前置知识1:
Java中,方法的参数传递只有值传递,但是对于引用类型,传递的是地址的值
。
前置知识2:Java数据类型:
因此题目中,change()方法传递的char[]数组和String,都是引用类型,也就是说传递的是地址。
那么,change()中对于char[]数组的修改是生效的,test 会变成 best。
但是对于String类型,它是不可变的,change()方法中对于str的赋值,底层会赋值一份str,再修改,而不会影响原有的str。
1、常量与常量的拼接结果在常量池,原理是编译期优化
2、常量池不会存在相同内容的常量。
Intern()方法设计的初衷,就是重用String对象,以节省内存消耗。
str.intern()的作用:判断字符串常量池中是否存在str表示的字符串:
调用String.intern()方法后,堆中的字符串实例并不会立即被销毁,但是通过intern()方法返回的引用会被重新赋值为常量池中的引用,这可能会导致原本指向堆中字符串的引用失效,从而可能会被垃圾回收器回收,从而节省内存消耗.
String s1 = new String("hello");
String s2 = "hello";
String s3 = s1.intern();
System.out.println(s1 == s2); // false,不同的引用
System.out.println(s2 == s3); // true,共享常量池中的引用
String s = newString(“1”),生成了常量池中的“1” 和堆空间中的字符串对象。
s.intern(),这一行的作用是s对象去常量池中寻找后发现"1"已经存在于常量池中了。
String s2 = “1”,这行代码是生成一个s2的引用指向常量池中的“1”对象。
结果就是 s 和 s2 的引用地址明显不同。因此返回了false。
String s3 = new String(“1”) + newString(“1”),这行代码在字符串常量池中生成“1” ,并在堆空间中生成s3引用指向的对象(内容为"11")。注意此时常量池中是没有 “11”对象的。
s3.intern(),这一行代码,是将 s3中的“11”字符串放入 String 常量池中,此时常量池中不存在“11”字符串,JDK1.6的做法是直接在常量池中生成一个 “11” 的对象。
但是在JDK1.7中,常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用直接指向 s3 引用的对象,也就是说s3.intern() ==s3会返回true。
String s4 = “11”, 这一行代码会直接去常量池中创建,但是发现已经有这个对象了,此时也就是指向 s3 引用对象的一个引用。因此s3 == s4返回了true。
强引用(StrongReference):最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“object obj=new object()"这种引用关系。无论任何情况下只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
软引用(SoftReference):在系统将要发生内存溢出之前,将会把这些对象列入回收范围之中进行第二次回收。如果这次回收后还没有足够的内存,才会抛出内存溢出异常
弱引用(weakReference):被弱引用关联的对象只能生存到下一次垃圾收集之前。当垃圾收集器工作时,无论内存空间是否足够,都会回收掉被弱引用关联的对象。
虚引用(PhantomReference) :一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
所谓"GC Roots"根集合就是一组必须活跃的引用。
基本思路:
Java语言提供了对象终止(finalization)机制来允许开发人员提供对象被销毁之前的自定义处理逻辑。
当垃圾回收器发现没有引用指向一个对象,即:垃圾回收此对象之前,总会先调用这个对象的finalize()方法。
finalize ()方法允许在子类中被重写,用于在对象被回收时进行资源释放.通常在这个方法中进行一些资源释放和清理的工作,比如关闭文件、套接字和数据库连接等。
Java的finalization机制与C++的析构函数有一些相似之处,但也有所不同
System.gc()和Runtime.getRuntime().gc()都用于显式触发垃圾回收,用于建议JVM执行垃圾回收。这种方式与System.gc()的效果是一样的,它也不能保证立即执行垃圾回收,仍然取决于JVM的决策。
内存溢出:堆区没有空闲内存,并且垃圾收集器也无法提供更多的内存,称为内存溢出。
内存泄露:对象不会再被程序用到了,但是GC又不能回收他们的情况,称为内存泄露。
安全点:程序执行时并非在所有地方都能停顿下来开始Gc,只有在特定的位置才能停顿下来开始GC,这些位置称为“安全点(Safepoint) ”。
如何在GC发生时,检查所有线程都跑到最近的安全点停顿下来呢?
Safepoint机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的 Safepoint 。但是,程序“不执行”的时候呢?例如线程处于Sleep 状态或Blocked状态,这时候线程无法响应JVM的中断请求,“走”到安全点去中断挂起,JVM也不太可能等待线程被唤醒。对于这种情况,就需要安全区域( Safe Region)来解决。
安全区域:安全区域是指在一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始cc都是安全的。我们也可以把 safe Region看做是被扩展了的safepoint。
垃圾回收器(Garbage Collector,GC)