Java文件的运行过程
首先从计组的角度简单聊聊电脑上文件的运行过程:
电脑上所有的程序都存在硬盘上,双击点开时才会进入内存运行。
内存中的数据是闪存,只要断了电,就没有电压电势压着电容,数据就会消失。
在硬盘上的程序文件只有在运行时,才会加载到内存,不用他时不会进入内存。
java代码文件写完之后,会以文件的形式保存到硬盘(.java文件),真正运行起来之后会进入内存(.class文件)。每一个独立运行的程序都会独立占一块内存互不干扰。
java编译之后运行时,信息进入内存之后,会以C语言的形式记录。
java文件运行时会申请一个足够大空间,是借助C语言数组申请的空间,这个空间是操作系统分配给他的。
JVM的数据区域
java自己的运行空间叫做JVM(由操作系统分配给他的一块内存空间),在这个大空间里有一个方法区,方法区里有静态常量池和类常量池。
java程序运行时,类常量池会存放class类。被static修饰的方法和属性在进入类常量池的同时也会进入静态常量池备份。
方法的执行是在栈区域中实现的,最开始是从main方法开始执行(拷贝一份进入栈,栈帧入栈)
在这个栈中同一时刻只能有一个方法运行,方法运行时拷贝入栈,运行完自动出栈(出栈即为标记为无效,下一个方法入栈时,将无效的方法覆盖掉)。
执行此语句时,会在堆区域中创建新的person对象个体,假设类常量池中存放的person为设计图纸,那么堆区域中存放的是按照图纸创造的实际对象。
类常量池中存放的是类(理论图纸),堆区域中存放的是对象(实际个体)
静态常量池中的函数方法(m2)只有一份,非静态的方法在每个区域内都会有一份(栈区域堆区域等共有)
对于堆区域中,被new出来东西对象都是有默认值的,但是方法区的没有赋值之前都是没有默认值的
字符串常量池存在于堆区域中
arr2是字符串数组(引用类型),数组中存放的是地址,字符串最终存储到字符串常量池中
如果将字符串数组中改变某一个字符串,那么他本身不可修改,变的是数组中存放的地址,修改后的新地址会指向字符串常量池中新的字符串(如果没有那就在字符串常量池新建)
当引用类型等于某个值的时候,一定是自己的指向发生了改变
java语言中的引用类型和C语言中的指针用法是一样的(因为其底层就是C语言指针类型实现的)C语言中指针等于指针也是改变指向
x2类中arr2字符串数组new出来了一个新字符串数组,一定是其数组内存放的地址发生变化,指向了字符串常量池中新的字符串
此时x2中arr2数组中原来记录的字符串地址,无任何指向,此时自动标记为无效。堆中的数据在没人调用时就会被删掉,但是常量池中的数据不会被删掉,除非常量池的空间不够用时,才会删掉部分不必要的数据。
在内存中,相邻的两个变量,其地址也是相邻的
引用类型等于引用类型,就是要保持相同指向
如果让x1、x2、x3等于null,那么栈区域中相应的地址指向被清空。堆区域中的数据没有来自栈区域的指向,被标记为无效(内存空间回收)
每个类里面静态的(被static修饰的)只有一份,存在于静态常量池中 ,没有被static修饰的每个区里面都有一份
在栈里面不断有方法的进出,这就是一条线程,main方法的线程是主线程。
对于栈中的线程,不同线程之间是同时交错运行的,不用排队,但在同一条线程中的方法是排队执行的。
t1、t2是两个线程
t1、t2.start() 指的是进入就绪态,并不是直接开始执行,如果是立即执行的话,会对正在执行的两个线程有破坏。
因为CPU的核同一时刻只能处理一件事,多个线程同时执行其实是内核的快速切换。进入就绪态的线程,谁先执行由操作系统决定,不一定按进入就绪态的顺序
synchronized锁的作用:
加锁的作用,就是谁来掉用这个方法,谁就会独占整个对象。当某个线程掉用这个方法的时候的时候,其他线程不能调用这个对象里面的其他东西。必须等这个方法执行完了才能访问。
t1线程调用加锁的方法m1,则t2线程不能同时调用(m1方法所在对象中的)m3方法。直至m1方法执行完之后,t2线程才能调用(m1方法所在对象中的)m3方法。
m1方法所在对象:
程序计数器区域是java内存中的第四大区域
线程是在栈中执行的,每个线程都会有自己的一份程序计数器,负责记载上面时候方法入栈开始执行,什么时候方法执行完毕出栈
本地方法栈(调用操作系统内核的方法去运行):
程序的运行,最终要转换为硬件的运行,每个硬件都会有其对应的驱动程序。操作系统内核来直接对接驱动。
我们通过写程序调动操作系统内核,来调动驱动,进而调动硬件。
理论上我们可以直接通过程序来调动驱动,但是操作系统不让! 因为驱动只能有一方来调动,程序的调动会和操作系统的调动相冲突,这是不安全的。
程序调动操作系统内核需要用到本地方法栈:
只有将java栈区域中自定义的方法线程转化为本地方法栈中的方法线程,然后调动本地方法栈中的线程方法,才是真正的调动操作系统内核,最终体现为硬件执行。
本地方法栈是操作系统内核的方法栈, 不是java独有,任何高级语言都会有这个区域,作用是和操作系统内核沟通。
在栈区域和本地方法栈的背后,还有一套看不见的区域,来推动这两部分相衔接(负责这两部分的翻译工作),这部分区域叫守护线程。
栈区域的线程栈是由数组实现的,栈中的每个方法也是由数组实现的,整体相当于数组套数组。
每一个方法本身也是个栈结构,变量作用域也是通过变量出栈来控制的,没遇到一个},都会有该{}内部新声明的变量出栈,不能在调用。