一、Java文件编译的过程
1.程序员编写的.java文件;
2.由javac编译成字节码文件.class(为什么编译成class文件,因为JVM只认识class文件)
3.再由JVM编译成电脑认识的文件(对于电脑系统来说文件代表一切)
二、为什么说Java是跨平台文件
这个跨平台是中间语言(JVM)实现的跨平台;Java有JVM从软件层面屏蔽了底层硬件、指令层面的细节让他兼容各种系统。
三、jdk、jre、JVM的区别
Jdk:Java development kit; Java开发工具包;
Jre:Java runtime environment; Java运行环境;
Jvm:Java virtual machine;Java虚拟机;
JDK = JRE + 开发工具集(例如javac编译工具等)
JRE = JVM + Java SE 标准类库;
JDK中的主要工具包:
java.lang:包含一些Java语言核心类,如 string、math、integer、system、thread;
Java.util:包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数
Java,net:包含执行与网络相关的操作的类和接口
Java.io:包含能提供多种输入和输出功能的类
Java.text:包含一些Java格式化相关的类
Java.sql:包含了Java进行jdbc数据库编程的相关类、接口
Java.awt:包含了构成抽象窗口工具集的多个类,这些类被用来构建和管理应用程序的图形用户界面。
四、JVM的生命周期
**(1)虚拟机的启动:**通过引导类加载器(bootstrap classloader)创建一个初始类(initial class)来完成,这个类是由虚拟机的具体实现指定的。
**(2)虚拟机的执行:**一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序;程序开始执行时他才运行,程序结束时他就结束;执行一个所谓的Java程序时,真正执行的时Java虚拟机的进程。
**(3)虚拟机的退出:**1、正常执行结束;2、异常或错误而终止;3、操作系统出错;4、调用方法exit结束。
五、JVM的主要作用
进行内存管理。
六、类加载器子系统
类加载器子系统负责从文件系统或网络中加载class文件,class文件在文件开头有特定的文件标识:ca fe ba be ;类加载器只负责class文件的加载,至于它是否可以运行,则有execution engine 决定。加载的类信息存放于一块称为方法区的内存空间,除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量。
类加载器子系统的工作分为三部分:加载阶段、链接阶段、初始化阶段。
1、加载阶段
通过一个类的全限定名获取定义此类的二进制字符流。将这个字符流所代表的静态存储结构转化为方法区的运行时数据结构。在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口。
由引导类加载器、扩展类加载器、系统类加载完成。
Bootstrap class loader ----> extension class loader —> system class loader —> user defined class loader.
**启动类加载器(Bootstrap class loader)😗*由C/C++实现的,嵌套在JVM内部。它用来加载Java的核心类库,用于提供JVM自身需要的类。并不继承自java.lang.classloader,没有父类加载器。加载扩展类和系统类加载器,并指定为他们的父类加载器。出于安全考虑bootstrap 启动类加载器只加载包名为java/javax/sun等开头的类。
**扩展类加载器(exetension class loader):**由Java语言编写,由sun.misc.lancher
a
p
p
c
l
a
s
s
l
o
a
d
e
r
实
现
。
派
生
与
c
l
a
s
s
l
o
a
d
e
r
类
。
父
类
加
载
器
为
启
动
类
加
载
器
。
从
j
a
v
a
,
e
x
t
,
d
i
r
s
系
统
属
性
所
指
定
的
目
录
中
加
载
类
库
,
或
从
J
D
K
的
安
装
目
录
的
j
r
e
/
l
i
b
/
e
x
t
子
目
录
下
加
载
类
库
。
如
果
用
户
创
建
的
J
A
R
放
在
此
目
录
下
,
也
会
有
扩
展
类
加
载
器
加
载
。
∗
∗
∗
∗
系
统
类
加
载
器
(
s
y
s
t
e
m
c
l
a
s
s
l
o
a
d
e
r
)
:
∗
∗
∗
∗
由
J
a
v
a
语
言
编
写
,
由
s
u
n
.
m
i
s
c
.
l
a
n
c
h
e
r
appclassloader实现。派生与class loader类。父类加载器为启动类加载器。从java,ext,dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库。如果用户创建的JAR放在此目录下,也会有扩展类加载器加载。 ****系统类加载器(system class loader):****由Java语言编写,由sun.misc.lancher
appclassloader实现。派生与classloader类。父类加载器为启动类加载器。从java,ext,dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库。如果用户创建的JAR放在此目录下,也会有扩展类加载器加载。∗∗∗∗系统类加载器(systemclassloader):∗∗∗∗由Java语言编写,由sun.misc.lancherappclassloader实现。派生于class loader类。父类加载器为扩展类加载器。他负责加载环境变量class path或系统属性java.class.path指定路径下的库。该类加载时程序中默认的类加载器,一般来说Java应用的类都是有它来完成加载。通过classloader#getsystemclassloader()方法可以获取到该类加载器。
**用户自定义加载器(user defined classloader):**在Java的日常应用程序开发中,类的加载几乎是由上述3中类加载器相互配合执行的,但在必要时,我们还可以自定义类加载器,来定制类的加载方式。
为什么要自定义类的加载器:
1、隔离加载类;2、修改类加载的方式;3、扩展加载源;4、防止代码泄露。
自定义类加载器的实现步骤:1、通过继承抽象类java.lang.classloader抽象类的方式2、把自定义的类加载逻辑下载findclass()方法中。3、如果没有过于复杂的需求,可直接集成URLclassloader类,避免编写findclass()方法及获取字节码流方式,是自定义加载器编写更加简洁。
获取class loader的途径:1、获取当前类的class.getclassloader();2、获取当前线程上下文的class loader。Thread.currentthread().getcontextclassloader();3、获取系统的class loader:classloader.getsystemclassloader();4、获取调用者的classloader:DriverManager.getcallerclassloa
der();
2、链接,包括:验证、准备、解析三个阶段
①验证:目的在于确保class文件的字节流中包含的信息符合当前虚拟机的要求,保证被加载类的正确性,不会危害虚拟机的自身安全。主要包括四种验证:文件格式验证、源数据验证、字节码验证、符号引用验证。
②准备:为类变量分配内存并且设置该类变量的默认初始值,即零值。这里不包含用final修饰的static,因为final在编译时就分配好了,准备阶段会显示初始化,这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象仪器分配到Java堆中。
③解析:将常量池中的符号引用转换为直接引用的过程。事实上,解析操作往往会伴随着JVM在执行完初始化之后再执行。符号引用就是一组符号来描述所引用的目标。直接引用就是直接指向目标的指针,相对偏移量或间接定位到目标的句柄,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT_CLASS_INFO、CONSTANT_FIELDREF_INFO、CONSTANT_METHODREF_INFO等。
3、初始化
初始化阶段就是执行类构造器方法()的过程。此方法不需要定义,是javac编译器自动搜集类中的所有类变量的赋值动作和静态代码块中的语句合并来的。构造器方法中指令按照语句在源文件中出现的顺序执行。()不同于类的构造器(构造器是虚拟机视角下的())。若该类具有父类,JVM会保证子类的()执行之前,父类的()已经执行完毕。虚拟机必须保证一个类的()方法在多线程下被同步加锁。
七、双亲委派机制及沙箱安全机制
Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将他的class文件加载到内存生成class对象,而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理,他是一种任务委派模式。
工作原理:如果一个类加载器受到类加载请求,他并不会自己先去加载,二是把这个请求委托给父类的加载器去执行。如果父类加载器还存在其父类加载器则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器。如果父类加载器可以完成类加载任务,就成功返回,若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。优势:1、避免类的重复加载;2、保护程序安全,防止核心API被随意篡改。
**沙箱安全机制:**自定义string类,但是在加载自定义string类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会率先加载JDK自带的文件(rt.jar包中java/lang/string.class)报错信息说没有main方法就是因为加载的是rt.jar包中的string类。这样可以保证对Java核心源代码的保护,这就是沙箱安全机制。
八、Java运行时数据区
Java运行时数据区:方法区、堆、本地方法栈、虚拟机栈、程序计数器;
其中方法区和堆为多线程共享;本地方法栈、虚拟机栈、程序计数器为线程私有;
1、方法区
是线程共享的区域,用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。静态变量的引用对象在堆中。
他有个别名叫non-heap,当方法区无法满足内存分配需求时,会抛出outofmemoryError异常;
创建对象的方式:
①new以及对象的实例化静态方法②class的newinstance()反射的方式只能调用空参构造器,权限必须是public。③constuctor的newinstance(xxx)反射的方式,可以调用空参、带参的构造器,权限无要求。④使用clone()不调用任何构造器,当前类需要实现clonable接口实现clone();⑤使用反序列化:从文件中、网络中获取一个对象的二进制流⑥第三方库objenesis
创建对象的步骤:
①判断对象对应的类是否加载、链接、初始化;②为对象分配内存:如果内存完整——采用指针碰撞的方式分配;如果内存不完整——采用空闲列表分配,虚拟机需要维护一个列表。③处理并发安全问题:采用CAS失败重试、区域加锁保证更新的原子性,每一个线程预先分配一块TLAB(线程私有缓冲区)——通过-xx:+/-useTlab参数来设定。④初始化分配到的空间:所有属性设置默认值,保证对象实例字段在不赋值时可直接使用⑤设置对象的对象头⑥执行init方法进行初始化。
2、堆
堆是Java虚拟机所管理的内存中最大的一块,是被所有线程共享的,在这里还可以划分线程私有的缓冲区(TLAB),堆在虚拟机启动时创建,一旦被创建它的大小就确定了,其大小可以通过-xms来设置起始内存,-xmx来设置最大内存,通常将两个值设置为相同值,其目的是了能够再垃圾回收机制清理完堆内存后不需要重新分隔计算堆区的大小,从而提高性能。堆可以处在物理上不连续的内存空间中,但在逻辑上它应该被视为连续的,此内存区域的唯一目的就是存放对象实例。所有的对象实例以及数组都要在堆上分配,当方法结束时,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。Java堆是垃圾回收器管理的主要区域,也被称为GC堆。当堆中没有内存可以完成实例分配,并且堆也无法在扩展时,将会抛出outofmemoryError异常。
对主要分为新生代、老年代和永久代(元空间JDK1.8开始)
其中新生代又被分为Eden区和survivor区;survivor区又分为survivor0区(from)和survivor一区(to)。
新生代和老年代的默认比例为1:2,新生代中伊甸区和两个幸存者区的比例为8:1:1。所有的Java对象都是在伊甸区被new出来的,绝大部分的Java对象的销毁都是在新生代进行的。
内存布局:
①对象头:包含两部分运行时元数据、类型指针。
运行时元数据:包括哈希值、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间ID。
类型指针:指向类元数据instance class,确定该对象所属的类型;
说明:如果是数组,还需要记录数组的长度;
②实例数据:
它是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段,包括从父类继承下来的和本身拥有的字段。
规则:相同宽度的字段总是被分配到一起,父类中定义的变量会出现在子类之前,如果compactfields参数为true:子类的窄变量可能插入到父类变量的空隙。
③对齐填充:仅仅起到占位符的作用
**对象访问定位:**通过栈帧中的对象引用指向堆区,通过堆区的源数据指针指向方法区。创建对象的目的是为了使用它,JVM是通过栈上局部变量表中的reference访问到内部的对象实例。对象访问方式主要有两种:句柄访问和直接指针。
直接内存
是在Java堆外的,直接向系统申请的内存区域。速度优于Java堆,有可能OOM异常,由于直接内存在Java堆外,因此它的大小不会直接受限于-xmx指定的最大堆大小,但是系统内存是有限的,Java堆和直接内存的总和依然受限于操作系统能给出的最大内存。缺点:分配回收的成本较高;不受JVM内存回收管理。直接内存大小可通过MaxDirectMemorysize设置,如果不指定,默认与堆的最大值-xmx参数值一致
3、程序计数器(PC寄存器)
用来存储指向下一条指令的地址,即,将要执行的指令代码,他是一块很小的内存区域,几乎可以忽略不计,但也是运行速度最快的存储区域,也是虚拟机栈唯一没有规定outofmemoryerror的区域。程序计数器会存储当前线程正在执行的Java方法的jvm指令地址,如果是在执行native方法,则是未指定值(undefine)。他是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
Q1:使用PC寄存器存储字节码指令地址有什么用?
因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从那开始继续执行。JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该被执行的字节码指令。
Q2:PC寄存器为什么会被设定为线程私有?
为了能够准确记录各个线程正在执行的当前字节码指令地址,最好的办法就是为每一个线程都分配一个PC寄存器。
并行VS并发:并行:在同一时刻有多条指令在多个处理器上同时执行。并发:是指一个处理器同时处理多个任务。
在Java中最小的执行单位是线程,线程是要执行指令的,执行的指令最终操作的就是我们的CPU。
4、本地方法栈
是Java虚拟机栈用于管理Java方法的服务;本地方法栈用于管理本地方法的调用,是线程私有的。允许被实现成固定或者可动态扩展的内存大小,本地方法是用C语言编写的,它的具体做法是本地方法栈中登记native方法,在execution engine执行时加载本地方法库。
5、虚拟机栈
Java虚拟机栈是线程私有的,它的生命周期和线程相同。栈是运行时的单位,堆是存储时的单位。栈解决的是程序运行的问题,即程序如何执行或者说如何处理数据。堆解决的是数据存储问题,即数据怎么放,放在哪。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧,对应着一次次的Java方法调用。虚拟机栈助管Java程序的运行,他保存的是方法的局部变量、操作数栈、动态链接和出口(方法的调用和返回)。栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。JVM直接对栈的操作有两个:意识每个方法执行伴随着进栈;二是执行结束后的出栈工作。对于栈来说不存在垃圾回收问题。如果虚拟机栈的内存大小固定,线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机栈将会抛出一个stackoverflow error异常。如果Java虚拟机可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个outofmemoryerror异常。可以通过-XSS设置栈的大小。
栈帧的内部:局部变变量表、操作数栈、动态链接、方法返回地址、一些附加信息。
5.1 局部变量表
也称为局部变量数组或者本地变量表,定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各种基本数据类型,对象引用以及returnAddress类型(return Address中保存的是return后要执行的字节码的指令地址)。由于局部变量表建立在线程的栈上,是线程私有数据,因此不存在数据安全问题。局部变量表所需的容量大小是在编译器确定下来的,并保存在方法的CODE属性的maximum localvariables中。在方法运行期间是不会改变局部变量的大小的。局部变量表最基本的存储单元是slot变量槽。32为以内的类型只占用一个slot如返回值类型,64为的类型占用两个slot如long和double。同时byte short char float boolean在存储前被转换成int。JVM会为局部变量表中的每一个slot分配一个访问索引,通过这个索引即可成功访问到局部变量值。如果当前帧是由构造方法或实例方法创建的,那么该对象引用this将会存放在index为0的的slot处。栈帧中额局部变量表中的槽位是可以重复利用的,如果一个局部变量过了其作用域,那么在其作用域之后的新的局部变量就很有可能会复用过期局部变量的槽位,从而达到节省资源的目的。
5.2操作数栈
操作数栈主要是在方法执行过程中,根据字节码指令往栈中写入数据或提取数据,即入栈(push)/出栈(pop)。主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。操作数栈就是JVM执行引擎的一个工作区,当一个方法开始执行的时候,新的栈帧也会随之被创建出来,这个方法的操作数栈是空的。每个操作数栈都会拥有一个明确的栈深度,用于存储数值,其所需的最大深度在编译期就定义好了,保存在方法的code属性中,为max_value的值。栈中的任何一个元素都是可以任意的Java数据类型。32bit的类型占用一个栈单位深度,64位的类型占用两个栈单位深度。操作数栈并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈出栈操作来完成一次数据访问。如果被调用的方法带有返回值的话,其返回值将被压入当前栈帧的操作数栈中,并更新PC寄存器中下一条需要执行的字节码指令。Java虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈。栈顶缓存技术:将栈顶元素全部缓存在物理CPU的寄存器中,以此降低对内存的读写次数,提升执行引擎的执行效率。
5.3动态链接
每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用,包含这个引用的目的是为了支持当前方法的代码能够实现动态链接。在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在class文件的常量池中,动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
方法的调用:静态链接(早期绑定)、动态链接(晚期绑定)
**静态链接:**当一个字节码文件被装进JVM内部时,如果被调用的目标方法在编译期可知,且运行期保持不变,这种情况下将调用方法的符号引用转换为直接引用的过程成为静态链接。
**动态链接:**如果被调用的方法在编译期无法被确定下来,也就是说只能够在程序运行期间将调用方法的符号引用转换为直接引用,称为动态链接。
**方法重写的本质:**1、找到操作数栈顶的第一个元素所执行的对象的实际类型,记作C。2、如果在C中找到与常量池中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回java.lang.IllegalAccessError异常。3、否则按照继承关系从下往上以此对C的各个父类进行第二步的搜索和验证过程。4、如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。
IllegalAccessError异常:程序试图访问或修改一个属性或调用一个方法,这个属性或方法,你没有权限访问,一般的,这个会引起编译器异常,这个错误如果发生在运行时就说明一个类发生了不兼容的改变。
5.4 方法返回地址
存放调用该方法的PC寄存器的值。而方法返回地址的类型存放在局部变量表中。
5.5 一些附加信息
栈帧中还允许携带与Java虚拟机实现相关的一些附加信息,如对程序调试提供支持的信息。
Q1:一个方法调用另一个方法会创建很多栈帧吗?
会,如果一个栈中有动态链接调用别的方法,就会去创建新的栈帧,栈中是由顺序的,一个栈帧调用另一个栈帧,另一个栈帧就会排在调用者下面。
Q2:栈指向堆是什么意思?
栈中要使用成员变量,栈中不会存储成员变量,只会存储一个应用地址,因此需要指向堆中存储的成员变量。
Q3:递归调用自己的话会创建很多帧吗?
递归的话会创建很多帧,而且递归的时候一定要注意设置有效的可到达的终止条件,否则会栈溢出。
九、执行引擎
虚拟机核心的组件就是执行引擎。
功能:将字节码指令解释、编译为对应平台上的本地机器指令,包括解释器、即时编译器、垃圾回收器。
①解释器:当Java虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容翻译为对应平台的本地机器指令执行。
②JIT编译器:就是虚拟机将源代码直接编译成和本地机器平台相关的机器语言。
**机器码指令:**各种用二进制编码方式表示的指令。
**指令:**把机器码中特定的0和1序列简化成对应的指令。
高级语言----编译过程---->汇编语言-----汇编过程----->机器指令----->CPU
字节码是一种中间状态的二进制代码,需要直译器转译后才能成为机器码。主要为了实现特定软件运行和软件环境。字节码的实现方式是通过编译器和虚拟机器。编译器将源码编译成字节码,特定平台上的虚拟机器将字节码转译为可以直接执行的指令。
Java代码的执行分类:
①将源代码编译成字节码文件,然后在运行时通过解释器将字节码文件转化为机器码执行;②编译执行(直接编译成机器码)。现代虚拟机为了提高执行效率,会使用即时编译技术将方法编译成机器码后再执行。Hotspot VM 采用解释器与即时编译器并存的结构。
十、String
String的字符串常量池是一个固定大小的hashtable,默认长度为60013,可用-xx:stringtablesize设置,下限为1009.
常量与常量的拼接结果在常量池,原理是编译期优化;常量池中不会存在相同内容的常量;只要其中有一个是变量,结果就在堆中,原理StringBuilder;如果凭借的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中并返回此对象地址。
十一、垃圾回收机制
**垃圾:**在运行程序中没有任何指针指向的对象;
**GC的作用域:**方法区、堆。其中堆是GC的工作重点,频繁收集新生代,较少收集老年代,基本不动永久代(元空间)。
垃圾回收算法:
1、标记阶段
①引用计数算法:对每个对象保存一个整型的引用计数器,用于记录对象被引用的情况。引用对象A,A的引用计数器加1,引用失效减1.只要对象A的引用计数器的值为0,即表示对象A不可能再被引用,可回收。
优点:实现简单,垃圾对象便于辨识;判定效率高,回收没有延迟;
缺点:增加了存储空间的开销。时间开销。无法处理循环利用。
②可达性分析(根搜索算法):“GC ROOTS”根集合就是一组必须活跃的引用。可达性分析算法是以根对象集合为起始点,按照从上至下的方式搜索根对象集合所连接的目标是否可达,如果不可达则意味着该对象已经死亡,可以标记为垃圾对象。
GC Roots包括以下几类元素:①虚拟机栈中引用的对象②本地方法栈内本地方法引用的对象③方法区中类静态属性引用的对象④方法区中常量引用的对象⑤所有被同步锁synchronized持有的对象⑥Java虚拟机内部的引用:基本数据类型对应的class对象、常驻的异常对象、系统类加载器⑦反应Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等⑧分代收集和局部回收。
2、对象的finalization机制
垃圾回收对象之前,会先调用这个对象的finalize()方法,用于在对象被回收时进行资源释放,如关闭文件、数据库连接等。
对象的三种状态:可触及的、可复活的、不可触及的。
判断一个对象是否可回收,至少要经历两次标记过程。
3、清除阶段
①标记-清除算法:当堆中有效内存空间被耗尽时,就会停止整个程序,然后进行标记和清除。
标记:collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。
清除:collector对堆内存从头到尾进行线性遍历,如果发现某个对象Header中未标记为可达对象,则将其回收。
缺点:效率不高,需要停止程序,用户体验差,空闲内存不连续,产生内存碎片。
②复制算法:将内存平均分成两部分没然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空。
③标记整理算法:标记-整理法是标记-清除法的一个改进版。同样,在标记阶段,该算法也将所有对象标记为存活和死亡两种状态;不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清理,而是将所有存活的对象整理一下,放到另一处空间,然后把剩下的所有对象全部清除。这样就达到了标记-整理的目的。
④分代收集算法:不同生命周期的对象可以采取不同的收集方式,以便提高回收率。
新生代:在新生代,每次垃圾收集器都发现有大批对象死去,只有少量存活,采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
老年代:而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须“标记清除法或者标记整理算法进行回收。
4、垃圾收集器
垃圾收集器是垃圾回收算法的具体体现。垃圾收集器的常见指标:
吞吐量:运行用户代码的时间占总运行时间的比例;
暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间;
内存占用:Java堆区所占的内存大小;
垃圾收集开销:吞吐量的补数,垃圾收集所用的时间与总运行时间的比例;
收集频率:相对于应用程序的执行,收集操作发生的频率;
目标:在最大吞吐量优先的情况下,降低停顿时间。
①串行收集器:Serial、Serial old
②并行收集器:ParNew、Parallel Scavenge、parallel old;
③并发收集器:CMS、G1
————————————————————————————
①新生代收集器:Serial、ParNew、Parallel Scavenge;
②老年代收集器:Serial old、parallel old、CMS;
③整堆收集器:G1
————————————————————————————
十二、本地方法接口
本地方法是指一个通过非Java代码实现的Java方法。1、与Java环境外交互;有时Java应用需要与Java外面的环境交互,这是本地方法存在的主要原因;2、与操作系统交互;3、sun’s Java.
十三、JVM可视化工具
1、为什么要可视化工具
开发大型 Java 应用程序的过程中难免遇到内存泄露、性能瓶颈等问题,比如文件、网络、数据库的连接未释放,未优化的算法等。随着应用程序的持续运行,可能会造成整个系统运行效率下降,严重的则会造成系统崩溃。为了找出程序中隐藏的这些问题,在项目开发后期往往会使用性能分析工具来对应用程序的性能进行分析和优化。
2、jconsole
Java 5引入了JConsole,是一个内置 Java 性能分析器,可以从命令行或在GUI shell中运行。可以轻松地使用JConsole来监控Java应用程序性能和跟踪Java中的代码。启动JConsole:点击jdk/bin 目录下面的jconsole.exe 即可启动,然后会自动自动搜索本机运行的所有虚拟机进程。选择其中一个进程可开始进行监控。