JVM基础知识2---尚硅谷视频笔记整理

JVM知识2
一 Java栈
1 栈管运行,堆管存储,典型例子就是trycatch中打印的异常是栈的轨迹:
try{
}catch (Exception e){
e.printStackTrace();
}finally {
}
2 stack栈
栈也叫栈内存,主管java程序的运行,是在线程创建时创建,它的生命周期是跟随线程的生命周期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。8种基本类型的变量+对象的引用变量+实例方法都是在函数的栈内存中分配。
3 栈存储的是什么?
Java方法 = 栈帧
程序中的方法 这些方法在java虚拟机中就叫栈帧,一个方法对应一个栈帧
栈帧中主要保存3类数据:
(1) 本地变量(Local Variables):输入参数和输出参数及方法内的变量
如:public class JVMDemo {
//输入参数和输出参数以及方法内的变量
public int add(int x,int y){
int result=-1;
result=x+y;
return result;
}
public static void main(String[] args) {
System.out.println(“xm”);
}
}
(2) 栈操作(Operated Stack):记录入栈、出栈的操作
(3) 栈帧数据(Frame data):包括类文件,方法等等。
栈运行原理:
栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生栈帧F2也被压入栈,B方法又调用了C方法,于是产生栈帧F3也被压入栈。。。。。
执行完毕后,先弹出F3栈帧,再弹出F2栈帧,再弹出F1栈帧。。。。
遵循“先进后出/后进先出到”的原则
每个方法执行的同时都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。栈的大小和具体JVM的实现有关,通常在256K-756K之间,约等于1Mb左右。
Java栈运行时顺序:

上图在一个栈中有两个栈帧:
栈帧2是最先被调用的方法,先入栈,然后方法2又调用了方法1,栈帧1处于栈顶的位置,栈帧2处于栈底,执行完毕后,依次弹出栈帧1和栈帧2,线程结束,栈释放。
每一个执行方法都会产生一个栈帧,保存到栈(后进先出)的顶部,顶部就是当前方法,该方法执行完毕后会自动将此栈帧出栈。

  1. java栈可能导致的错误:
    public class JVMDemo {
    public static void m1(){
    m1();
    }
    public static void main(String[] args) {
    System.out.println(“xm”);
    m1();
    System.out.println(“xm2”);
    }
    }
    运行结果:
    Exception in thread “main” java.lang.StackOverflowError
    注意:这是一个error级别的错误
    最后栈帧中保存的数据如下图所示:

4 栈+堆+方法区的交互关系

HotSpot是使用指针的方式来访问对象的,java 堆中存放的是访问类元数据(描述数据的数据,也就是模板)的地址,reference存储的就直接是对象的地址。
5 堆Heap
一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的,类加载器读取了类文件后,需要把类,方法,常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行,堆内存分为三部分:
λ Young Generation Space新生区 Young/New
λ Tenure generation space 养老区 Old/Tenure
λ Permanent Space 永久区 Perm

堆内存逻辑上分为三部分:新生+老年+永久

java8以后永久存储区叫元空间

6 新生区
新生区是类的诞生,成长,消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分:伊甸区(Eden space)和幸存者区(Survivor space),所有的类都是在伊甸区被new出来的。幸存区有两个:0区(Survivor 0 space)和1区(Survivor 1 space)。当伊甸区的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸区进行垃圾回收(Minor GC),将伊甸区中的不再被其他对象所引用的对象进行销毁,然后将伊甸区中的剩余对象移动幸存0区。若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。那如果1区也满了呢?再移动到养老区。若养老区也满了,那么这个时候将产生MajorGC(FullGC),进行养老区的内存清理。若养老区执行了Full GC之后依然发现无法进行对象的保存,就会产生OOM异常“OutOfMemoryError”
如果出现java.lang. OutOfMemoryError:java heap space异常,说明java虚拟机的堆内存不够。原因有二:
(1) Java虚拟机的堆内存设置不够,可以通过参数-Xms,-Xmx来调整
(2) 代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)
简洁描述:
Eden满了,开启
GC = YGC = 轻GC
Eden基本全部清空
s0 = from
交换 ???
old养老区,满了,开启:
Full GC=FGC
Full GC多次,发现养老区空间也不够了
OOM(OutOfMemoryError)

7 java栈中变量的作用范围
class Person{
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class TestTransferValue {
public void changeValue(int age){
age=30;
}
public void changeValue2(Person person){
person.setName(“xxx”);
}
public void changeValue3(String str){
str=“xxx”;
}
public static void main(String[] args) {
TestTransferValue test=new TestTransferValue();
int age=20;
test.changeValue(age);
System.out.println(“age”+"---------"+age);

    Person person=new Person("abc");
    test.changeValue2(person);
    System.out.println("personName---------"+person.getName());

    String str="abc";
    test.changeValue3(str);
    System.out.println("string-----"+str);
}

}
运行结果:
age---------20
personName---------xxx
string-----abc

分析:(1)首先test.changeValue(age)传递的是值,这个参数age的作用域只存在于该函数中,而main方法中打印的age的作用域则存在于main方法中,。因此,打印的结果应该是main中定义的初始值20.
(2) test.changeValue2(person)此处的调用传递的是引用对象,也就是该引用指向的实际地址,此刻调用的方法中指向的和引用的指向是同一个地方。因此在changeValue2()方法中改变了person的name值,最终打印的值是改变后的值。
(3)test.changeValue3(str)此处的调用传递的是引用类型–字符串常量,字符串常量存放在字符串常量池中,其特点是,如果其中存在需要被引用的数据则直接使用,如果不存在则重新创建一份。在这个例子中,str这个参数传递给函数changeValue3(str)时,两者指向同一个值,如图所示,但是changeValue3()方法中将str指向”xxx”这个值在常量池中不存在,因此会在常量池中创建一个xxx,如图所示:,此时字符串常量池中存在两个字符串,分别由两个不同作用域的str指向,main()中打印的是str指向的是abc,因此,最后结果应该是abc.

8 GC回收
from区和to区,它们的位置和名分,不是固定的,每次GC后会交换
GC之后有交换,谁空谁是to
Java堆从GC的角度还可以细分为:新生代(Eden区,FromSurvivor区和ToSurvivor区)和老年代,如下图所示:

MinorGC的过程(复制->清空->互换)
1):eden、SurvivorFrom复制到SurvivorTo,年龄+1
首先,当Eden区满的时候会触发第一次GC还活着的对象拷贝到SurvivorFrom区,当Eden区再次触发GC的时候会扫描Eden区和From区域,对这两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域(如果有对象的年龄已经达到了老年的标准,则复制到老年代区),同时把这些对象的年龄+1
2):清空eden,SurvivorFrom
然后,清空eden和SurvivorFrom中的对象,也就是:复制之后有交换,谁空谁是to
3): SurvivorTo和SurvivorFrom互换
最后,SurvivorTo和SurvivorFrom互换,原SurvivorTo称为下一次GC时的SurvivorFrom区,部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活就存入老年代。

9 永久带

上图中实线部分表示JVM实际的内存区域,包括新生区和养老区,但是逻辑意义上包括新生区,养老区,永久区(元空间),其中新生区中的3部分包括Eden伊甸区,survivor0(s0)区,survivor1(s1)区,这三部分各自的比例是:
8:1:1。80%的线程的创建,应用和结束都发生在新生区。

实际而言,方法区(Method Area)和堆一样,是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码等待,虽然JVM规范将方法区描述为堆的一个逻辑部分,但它却还有一个别名做Non-Heap(非堆),目的就是要和堆分开。
对于HotSpot虚拟机,很多开发者习惯将方法区称之为”永久代(PermanentGen)”,但严格本质上说两者不同,或者说使用永久代来实现方法区而已,永久代是方法区(相当于是一个接口interface)的一个实现,jdk1.7的版本中,已经将原本放在永久代的字符串常量池移走。

简言之:方法区是种规范,实际的实现,在java8以前叫永久代,java8以后叫元空间。

永久代(java7之前有)
永久存储区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息(它加载的是一切根本的类元素,犹如阳光,空气,水,几乎不会被回收,比如:rt.jar包),被装在进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占用的内存。

堆参数调优
10 堆内存结构解析
java7版本的堆内存结构图:

(1)堆内存物理上分为新生区和老年区,而逻辑上分为新生区,老年区,永久区,其中新生区又分为三部分,包括Eden区,s0,s1。新生区和老年区初始大小的比例是1:2.
(2) 堆参数调优调的主要是:-Xms(堆内存的初始化大小),-Xmx(堆内存的最大值),-Xmn(新生代的new区,该值一般使用默认值即可),主要调优参数是前两个,右下角的-XXPermSize(永久代大小)和-XX:MaxPermSize(永久代最大值)一般用不到(因为这是java7版本)
(3)对新生区的回收使用MinorGC,对老年区的回收使用MajorGC
java8版本的堆内存结构:

在java8种,永久代已经被移除了,被一个称为元空间的区域所取代,元空间的本质和永久代类似。
元空间与永久代最大的区别在于:永久代使用的是JVM的堆内存,但是j a va8以后的元空间并不在虚拟机中而是使用本机物理内存。
因此默认情况下,元空间的大小仅受本地内存限制。类的元数据放入native memory,字符串池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间控制。

11 堆内存调优简介
-Xms:设置堆内存初始分配大小,默认为物理内存的”1/64”
-Xmx:最大分配内存,默认为物理内存的”1/4”
-XX:+PrintGCDdetails:输出详细的GC处理日志
如何查看cpu的核数:
System.out.println(Runtime.getRuntime().availableProcessors());
运行结果:8
public class HeapTest {
public static void main(String[] args) {
//返回java虚拟机试图使用的最大内存量
long maxMemory=Runtime.getRuntime().maxMemory();
//返回java虚拟机中的内存总量
long totalMemory=Runtime.getRuntime().totalMemory();
System.out.println("-Xmx:MAX_MEMORY="+maxMemory+"(字节)、"+(maxMemory/(double)1024/1024)+“MB”);
System.out.println("-Xms:TOTAL_MEMORY="+totalMemory+"(字节)、"+(totalMemory/(double)1024/1024)+“MB”);
}
}
运行结果:
MAX_MEMORY=2147483648(字节)、2048.0MB
TOTAL_MEMORY=136314880(字节)、130.0MB
实际工程项目中,堆内存的最大值和初始值是一致的,主要是为了避免GC和应用程序争抢内存。

在idea中设置-Xmx,-Xms,-XX:+PrintGCDetails参数,并打印结果:

具体的设置是:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
运行结果:

[0.003s][warning][gc] -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead.
[0.011s][info ][gc,heap] Heap region size: 1M//堆大小1M
[0.026s][info ][gc ] Using G1
[0.026s][info ][gc,heap,coops] Heap address: 0x00000007c0000000, size: 1024 MB, Compressed Oops mode: Zero based, Oop shift amount: 3
[0.027s][info ][gc,cds ] Mark closed archive regions in map: [0x00000007fff00000, 0x00000007fff6fff8]
[0.027s][info ][gc,cds ] Mark open archive regions in map: [0x00000007ffe00000, 0x00000007ffe47ff8]
[0.045s][info ][gc ] Periodic GC disabled
MAX_MEMORY=1073741824(字节)、1024.0MB
TOTAL_MEMORY=1073741824(字节)、1024.0MB
[0.118s][info ][gc,heap,exit ] Heap
[0.118s][info ][gc,heap,exit ] garbage-first heap total 1048576K, used 3808K [0x00000007c0000000, 0x0000000800000000)
[0.118s][info ][gc,heap,exit ] region size 1024K, 4 young (4096K), 0 survivors (0K)
[0.118s][info ][gc,heap,exit ] Metaspace used 1103K, capacity 4576K, committed 4864K, reserved 1056768K
[0.118s][info ][gc,heap,exit ] class space used 110K, capacity 419K, committed 512K, reserved 1048576K
堆内存溢出在idea中实际体现:
参数配置:-Xms10m -Xmx10m -XX:+PrintGCDetails
import java.util.Random;
public class HeapTest {
public static void main(String[] args) {
//方法一
/* String str=“www.xm.com”;
while(true){
str+=str+new Random().nextInt(88888888)+new Random().nextInt(99999999);
/
//方法二
byte[] b=new byte[20
1024*1024];
}
}
}
运行结果
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space

垃圾回收时先是GC,GC,GC……,然后才是Full GC,然后又是GC,GC,。。。。。Full GC。。。。直到Full GC也不行了,就会抛出异常

12 GC垃圾收集器
[GC (Allocation Failure)//当分配失败就会发生GC
YoungGC后新生代内存占用 JVM堆总大小
GC类型 YoungGC前新生代内存占用 YoungGC后JVM堆内存占用
新生代总大小 YoungGC前JVM堆内存占用
[PSYoungGen:2048K->488K(2560K) 2048K->773K(9728K), 0.0015243 secs]
[Times:user=0.08 sys=0.02,real=0.00 secs]
YoungGC用户耗时 YoungGC系统耗时耗时 YoungGC实际耗时

FullGC,规律:
[名称:GC前内存占用->GC后内存占用(该区内存总大小)
[Full GC(Allocation Failure)
[PSYoungGen:OK->OK(2048K)]
[ParOldGen:3767K->3724K(7168K)] 3767K->3724K(9126K),
[Metaspace:3492K->3492K(1056768K)],0.0067654 secs]
[Times:user=0.00,sys=0.00,real=0.01 secs]

13 引用计数法

(1)GC总体概述:

JVM在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候♻回收的都是新生代。
因此GC按照回收的区域又分了两种类型,一种是普通GC (Minor GC),一种是全局GC(major GC或Full GC)

Minor GC和Full GC的区别:
普通GC(minor GC):只针对新生代区域的GC,指发生在新生代的垃圾收集动作,因为大多数java对象存活率都不高,所以minorGC非常频繁,一般回收速度也比较快。
全局GC(major GC or Full GC):指发生在老年代的垃圾收集动作,出现了Major GC,经常会伴随至少一次的Minor GC(但并不是绝对的)。Major GC的速度一般要比Minor GC慢上10倍以上。(原因是养老区更大,新生区更小)
(2)算法

如何判断对象是垃圾?如何回收垃圾?
假设已经确定某个对象是垃圾,使用一下四种算法回收:
(1) 引用计数法

后台的栈中记录中某个对象的引用次数,因此,被多次引用时,比如引用了几千次,会消耗大量的内存
class RefCountGC{
private byte[] bigSize=new byte[210241024];// 这个成员属性唯一的作用就是一点内存
Object instance=null;
}

public class HeapTest {
public static void main(String[] args) {
RefCountGC objectA=new RefCountGC();
RefCountGC objectB=new RefCountGC();
objectA.instance=objectB;
objectB.instance=objectA;
objectA=null;
objectB=null;
System.gc();
}
}
存在循环引用,若采用引用计数法是难以被回收的。
System.gc()表示显式的调用垃圾回收,但是并不是马上就会执行。实际工程中一般不会主动调用System.gc();
public class HeapTest {
public static void main(String[] args) {
}
}
这个程序总共有两个主要线程,一个是main线程,一个是看不见的垃圾回收线程,
(2)复制算法(Copying)–90%的使用率
年轻代中使用的是Minor GC,这种GC算法采用的是复制算法
复制算法的原理:
Minor GC会把Eden中的所有活的对象都移动到Su rvivor区域中,如果Survivor区中放不下,那么剩下的活的对象就被移到Old generation中,也就是一旦收集后,Eden就变成空的了。
当对象在Eden(包括一个Survivor区域,这里假设是from区域)出生后,在经过一次Minor GC后,如果对象还存活,并且能够被另外一块Survivor区域所容纳(上面已经假设为from区域,这里应为to区域,即to区域有足够的内存空间来存储Eden和from区域中存活的对象),则使用复制算法将这些仍然还存活的对象复制到另外一块Survivor区域(to区域)中,然后清理所使用过的Eden以及Survivor区域(即from区域),并且将这些对象的年龄设置为1,以后对象在Survivor区每熬过Minor GC,就将对象的年龄+1,当对象的年龄达到某个值(默认是15岁,通过-XX:MaxTenuring Thredhold来设定参数),这些对象就会成为老年代。
-XX:MaxTenuring Thredhold:设置对象在新生代中存活的次数

解释:年轻代中的GC,主要是复制算法,HotSpot JVM把年轻代分为了三部分,1个Eden区和2个Survivor区(分别叫From和to),默认比例是8:1:1,一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移动到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到老年代中。因为年轻代中的对象都是朝生夕死的(90%以上)。所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块用完,就将还活着的对象复制到另外一块上面,复制算法不会产生内存碎片。

在GC开始的时候,对象只会存在于Eden区和名为”From”的Survivor区,Survivor区”To”是空的,紧接着进行GC,Eden区中所有存活的对象都会被复制到”To”,而在”From”区中,仍存活的对象会根据它们的年龄值来决定去向,年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuring Thredhold来设置)的对象都会被移动到老年代中,没有达到阈值的对象都会被复制到”To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,”From”和”To”会交换它们的角色,也就是新的”To”就是GC前的”From”,新的”From”就是上次GC前的”To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一致重复这样的过程,知道”To”区被填满,”To”区被填满之后,会将所有对象都移动到老年代中。

复制算法它的缺点也是相当明显的:
a它浪费了一半的内存,这太命了
b 如果对象的存活率很高,我们可以极端一点,假设是100%存活,那么我们需要将所有对象都复制一遍,并将所有引用地址重置一边,复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变得不可忽视,所以从描述不难看出,复制算法要想使用,最起码对象的存活率要非常低才行,而且最重要的是,我们必须要克服50%的内存的浪费。
(3)标记清除(Mark-Sweep):简称标清
老年代一般是由标记清除或者是标记与标记整理的混合
原理:
算法分成标记和清除两个阶段,先标记出要回收的对象,然后统一回收这些对象,形如:

用通俗的话解释一下标记清除算法,就是当程序运行期间,若可以使用的内存被耗尽的时候,GC线程就会被触发并将程序暂停,随后将要回收的对象标记一遍,最终统一回收这些对象,完成标记清理工作接下来便让应用程序恢复运行。
标记清除算法的缺点:
a. 首先,它的缺点就是效率比较低(递归与全堆对象遍历),而且在进行GC的时候,需要停止应用程序,这回导致用户体验非常差劲。
b. 其次,主要的缺点则是这种方式清理出来的空闲内存是不连续的,因为死亡对象都是随机的出现在内存的各个角落的,现在把它们清除之后,内存的布局自然会乱七八糟。而为了应付这一点,JVM不得不维持一个内存的空闲列表,这又是一种开销,而且在分配数组对象的时候,寻找连续的内存空间会不太好找。

(3) 标记压缩(Mark-Compact)又叫:标记整理
也就是在标记清除算法的后面再加一步整理。
老年代一般是由标记清除或者是标记与标记整理的混合
原理:

在整理压缩阶段,不再对标记的对象做回收,而是通过所有存活对象都向一端移动,然后直接清除边界以外的内存。可以看到,标记的存活对象将会被整理,按照内存地址一次排列,而未被标记的内存会被清理掉,如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销。
缺点:
标记/整理算法唯一的缺点就是效率不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址,从效率上来说,标记/整理算法要低于复制算法。
工作中实际使用的是以上两种算法的结合:

该算法重点在于进行多次GC后压缩。
垃圾回收算法小总结:

可以看出,从效率上来说,复制算法是当之无愧的老大,但是却浪费了太多内存,而为了尽量兼顾上面提到的三个指标,标记/整理算法相对来说更平滑一些,但是效率上依然不尽人意,它比复制算法多了一个标记的阶段,又比标记/清除多了一个整理内存的过程。

有没有一种最有算法呢?
答:无,没有最好的算法,只有最合适的算法—分代收集算法。

年轻代(Young Gen)
年轻代特点是区域相对老年代较小,对象存活率低
这种情况复制算法的回收整理,速度是最快的,复制算法的效率只和当前存活对象大小有关,因而很适用于年轻代的回收,而复制算法内存利用率不高的问题,通过hotspot中的两个survivor的设计得到缓解。
老年代(Tenure Gen)
老年代的特点是区域较大,对象存活率高。
这种情况,存在大量存活率高的对象,复制算法明显变得不合适。一般是由标记清除或者标记清除与标记整理的混合实现。

面试题:

JMM(java内存模型)

  1. volatitle是java虚拟机提供的轻量级的同步机制:保证可见性
    不保证原子性
    禁止指令重排
  2. JMM的特点:可见性:主内存中的变量改了,需要某种机制通知其他线程
    原子性:某个操作不可分割
    有序性

JMM(java内存模型java Memory Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。

JMM关于同步的规定:
(1) 线程解锁前,必须把共享变量的值刷新回主内存
(2) 线程加锁前,必须读取主内存的最新值到自己的工作内存
(3) 加锁解锁是同一把锁
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域,而java
模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到线程自己的工作内存空间,然后对变量进行操作,操作完成后
再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,其简要访问过程如下图:

代码证明可见性:
//有一个类,包括一个属性和方法,该方法将number改为1205,有一个mian线程,一个A线程,从main线程进入后,A线程启动后让其睡3秒,3秒后number的值被改为1205,但此时没有一种机制通知main线程,此时,while循环一直无法跳出
class MyNumber{
int number=10;
public void addTo1205(){
this.number=1205;
}
}
public class VolatitleTest {
public static void main(String[] args) {
MyNumber myNumber=new MyNumber();
new Thread(()->{
//暂停一会儿线程
try {
System.out.println(“A-----------come in”);
Thread.sleep(3000);
myNumber.addTo1205();//将10修改为1205 System.out.println(Thread.currentThread().getName()+"\t update number,number value:"+myNumber.number);
}catch (Exception e){
e.printStackTrace();
}
},“A”).start();
while(myNumber.number==10){
//需要有一种机制告诉main线程,number已经修改为1205,跳出while
} System.out.println(Thread.currentThread().getName()+"\t mission is over");
}
}
运行结果:
A-------come in
A update number,number value:1205

修改后:
//此时,如果在变量前加上volatitle关键字,就自动获得了一种属性,一共这个存在主内存中的变量一改变,就会立刻通知其他线程
class MyNumber{
volatile int number=10;
public void addTo1205(){
this.number=1205;
}
}

public class VolatitleTest {
public static void main(String[] args) {
MyNumber myNumber=new MyNumber();
new Thread(()->{
//暂停一会儿线程
try {
System.out.println(“A-----------come in”);
Thread.sleep(3000);
myNumber.addTo1205();//将10修改为1205 System.out.println(Thread.currentThread().getName()+"\t update number,number value:"+myNumber.number);
}catch (Exception e){
e.printStackTrace();
}
},“A”).start();
while(myNumber.number==10){

    }      System.out.println(Thread.currentThread().getName()+"\t mission is over");
}

}
运行结果:
A-----------come in
main mission is over
A update number,number value:1205

静态的东西先加载进方法区,方法区放模版,只加载一次
静态先行,加载一次
静态的>构造块>构造方法

class codeXM{
public codeXM(){
System.out.println(“code的构造方法111。。。”);
}
{
System.out.println(“code的构造块222”);
}
static{
System.out.println(“code的静态代码块333”);
}
}
public class StaticTest {//主类
{
System.out.println(“code的构造块444”);
}
static{
System.out.println(“code的静态代码块555”);
}
public StaticTest(){
System.out.println(“code的构造方法666”);
}
public static void main(String[] args) {
System.out.println(“我是美丽的分割线777”);
new codeXM();
System.out.println("---------------");
new codeXM();
System.out.println("---------------");
new StaticTest();
}
}
运行结果:
//mian函数中的程序执行前,要先将静态的东西先加载进方法区(模板)
code的静态代码块555
我是美丽的分割线777
//调用其他的类方法:静态的>构造块>构造方法
code的静态代码块333
code的构造块222
code的构造方法111。。。
//调用其他类的方法,因为静态的只加载一次,因此只有构造块和构造方法
code的构造块222
code的构造方法111。。。
//调用主类中的方法,同样也是因为静态的只加载一次,因此只有构造块和构造方法
code的构造块444
code的构造方法666

不是原创,只是笔记整理,如有侵权,联系删除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值