内存是很是重要的系统资源,是硬盘和cpu的中间仓库及桥梁,承载着操做系统和应用程序的实时运行。JVM内存布局规定了JAVA在运行过程当中内存申请、分配、管理的策略,保证了JVM的高效稳定运行。不一样的jvm对于内存的划分方式和管理机制存在着部分差别(对于Hotspot主要指方法区)java
java虚拟机定了了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而建立,随着虚拟机退出而销毁。另一些则是与县城一一对应的,这些与线程对应的数据区域会随着线程开始和结束而建立和销毁。
如图,灰色的区域为单独线程私有的,红色的为多个线程共享的,即python
JVM中的程序计数寄存器(Program Counter Register)中,Register的命名源于CPU的寄存器,寄存器存储指令相关的现场信息。CPU只有把数据装载到寄存器才可以运行。JVM中的PC寄存器是对屋里PC寄存器的一种抽象模拟git
PC寄存器是用来存储指向下一条指令的地址,也即将将要执行的指令代码。由执行引擎读取下一条指令。github
利用javap -v xxx.class反编译字节码文件,查看指令等信息面试
1.使用PC寄存器存储字节码指令地址有什么用呢?/ 为何使用PC寄存器记录当前线程的执行地址呢? 由于CPU须要不停的切换各个线程,这时候切换回来之后,就得知道接着从哪开始继续执行
JVM的字节码解释器就须要经过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令
2.PC寄存器为何会设定为线程私有
咱们都知道所谓的多线程在一个特定的时间段内指回执行其中某一个线程的方法,CPU会不停滴作任务切换,这样必然会致使常常中断或恢复,如何保证分毫无差呢?为了可以准确地记录各个线程正在执行的当前字节码指令地址,最好的办法天然是为每个线程都分配一个PC寄存器,这样一来各个线程之间即可以进行独立计算,从而不会出现相互干扰的状况。
因为CPU时间片轮限制,众多线程在并发执行过程当中,任何一个肯定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。
这样必然致使常常中断或恢复,如何保证分毫无差呢?每一个线程在建立后,都会产生本身的程序计数器和栈帧,程序计数器在各个线程之间互不影响。算法
CPU时间片即CPU分配各各个程序的时间,每一个线程被分配一个时间段。称做它的时间片。
在宏观上:咱们能够同时打开多个应用程序,每一个程序并行不悖,同时运行。 但在微观上:因为只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每一个程序轮流执行。
并行与并发
并行:同一时间多个线程同时执行;
并发:一个核快速切换多个线程,让它们依次执行,看起来像并行,其实是并发编程
因为跨平台性的设计,java的指令都是根据栈来设计的。不一样平台CPU架构不一样,因此不能设计为基于寄存器的。
优势是跨平台,指令集小,编译器容易实现,缺点是性能降低,实现一样的功能须要更多的指令。数组
java虚拟机规范容许Java栈的大小是动态的或者是固定不变的缓存
/**
* 演示栈中的异常
*/
public class StackErrorTest {
public static void main(String[] args) {
main(args);
}
}
复制代码
咱们可使用参数-Xss选项来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大可达深度。 (IDEA设置方法:Run-EditConfigurations-VM options 填入指定栈的大小-Xss256k)安全
/**
* 演示栈中的异常
*
* 默认状况下:count 10818
* 设置栈的大小: -Xss256k count 1872
*/
public class StackErrorTest {
private static int count = 1;
public static void main(String[] args) {
System.out.println(count);
count++;
main(args);
}
}
复制代码
/**
* 栈帧
*/
public class StackFrameTest {
public static void main(String[] args) {
StackFrameTest test = new StackFrameTest();
test.method1();
//输出 method1()和method2()都做为当前栈帧出现了两次,method3()一次
// method1()开始执行。。。
// method2()开始执行。。。
// method3()开始执行。。。
// method3()执行结束。。。
// method2()执行结束。。。
// method1()执行结束。。。
}
public void method1(){
System.out.println("method1()开始执行。。。");
method2();
System.out.println("method1()执行结束。。。");
}
public int method2(){
System.out.println("method2()开始执行。。。");
int i = 10;
int m = (int) method3();
System.out.println("method2()执行结束。。。");
return i+m;
}
public double method3(){
System.out.println("method3()开始执行。。。");
double j = 20.0;
System.out.println("method3()执行结束。。。");
return j;
}
}
复制代码
每一个栈帧中存储着:
利用javap命令对字节码文件进行解析查看局部变量表,如图:
public class LocalVariablesTest {
private int count = 1;
//静态方法不能使用this
public static void testStatic(){
//编译错误,由于this变量不存在与当前方法的局部变量表中!!!
System.out.println(this.count);
}
}
复制代码
栈帧中的局部变量表中的槽位是能够重复利用的,若是一个局部变量过了其做用域,那么在其做用域以后申明的新的局部变量就颇有可能会复用过时局部变量的槽位,从而达到节省资源的目的。
private void test2() {
int a = 0;
{
int b = 0;
b = a+1;
}
//变量c使用以前以及经销毁的变量b占据的slot位置
int c = a+1;
}
复制代码
变量的分类:
栈 :可使用数组或者链表来实现
为何须要常量池呢
常量池的做用,就是为了提供一些符号和常量,便于指令的识别。
** 在JVM中,将符号引用转换为调用方法的直接引用与方法的绑定机制相关 **
对应的方法的绑定机制为:早起绑定(Early Binding)和晚期绑定(Late Bingding)。绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次。
随着高级语言的横空出世,相似于java同样的基于面向对象的编程语言现在愈来愈多,尽管这类编程语言在语法风格上存在必定的差异,可是它们彼此之间始终保持着一个共性,那就是都支持封装,集成和多态等面向对象特性,既然这一类的编程语言具有多态特性,那么天然也就具有早期绑定和晚期绑定两种绑定方式。
Java中任何一个普通的方法其实都具有虚函数的特征,它们至关于C++语言中的虚函数(C++中则须要使用关键字virtual来显式定义)。若是在Java程序中不但愿某个方法拥有虚函数的特征时,则可使用关键字final来标记这个方法。
子类对象的多态性使用前提:①类的继承关系②方法的重写
非虚方法
普通调用指令:
1.invokestatic:调用静态方法,解析阶段肯定惟一方法版本;
2.invokespecial:调用方法、私有及弗雷方法,解析阶段肯定惟一方法版本;
3.invokevirtual调用全部虚方法;
4.invokeinterface:调用接口方法;
动态调用指令:
5.invokedynamic:动态解析出须要调用的方法,而后执行 .
前四条指令固化在虚拟机内部,方法的调用执行不可人为干预,而invokedynamic指令则支持由用户肯定方法版本。其中invokestatic指令和invokespecial指令调用的方法称为非虚方法,其他的(final修饰的除外)称为虚方法。
/**
* 解析调用中非虚方法、虚方法的测试
*/
class Father {
public Father(){
System.out.println("Father默认构造器");
}
public static void showStatic(String s){
System.out.println("Father show static"+s);
}
public final void showFinal(){
System.out.println("Father show final");
}
public void showCommon(){
System.out.println("Father show common");
}
}
public class Son extends Father{
public Son(){
super();
}
public Son(int age){
this();
}
public static void main(String[] args) {
Son son = new Son();
son.show();
}
//不是重写的父类方法,由于静态方法不能被重写
public static void showStatic(String s){
System.out.println("Son show static"+s);
}
private void showPrivate(String s){
System.out.println("Son show private"+s);
}
public void show(){
//invokestatic
showStatic(" 大头儿子");
//invokestatic
super.showStatic(" 大头儿子");
//invokespecial
showPrivate(" hello!");
//invokespecial
super.showCommon();
//invokevirtual 由于此方法声明有final 不能被子类重写,因此也认为该方法是非虚方法
showFinal();
//虚方法以下
//invokevirtual
showCommon();//没有显式加super,被认为是虚方法,由于子类可能重写showCommon
info();
MethodInterface in = null;
//invokeinterface 不肯定接口实现类是哪个 须要重写
in.methodA();
}
public void info(){
}
}
interface MethodInterface {
void methodA();
}
复制代码
当一个方法开始执行后,只要两种方式能够退出这个方法: 一、执行引擎遇到任意一个方法返回的字节码指令(return),会有返回值传递给上层的方法调用者,简称正常完成出口;
二、在方法执行的过程当中遇到了异常(Exception),而且这个异常没有在方法内进行处理,也就是只要在本方法的异常表中没有搜素到匹配的异常处理器,就会致使方法退出,简称异常完成出口
方法执行过程当中抛出异常时的异常处理,存储在一个异常处理表,方便在发生异常的时候找处处理异常的代码。
栈帧中还容许携带与java虚拟机实现相关的一些附加信息。例如,对程序调试提供支持的信息。(不少资料都忽略了附加信息)
1.举例栈溢出的状况?(StackOverflowError)
2.调整栈的大小,就能保证不出现溢出么?
3.分配的栈内存越大越好么?
4.垃圾回收是否会涉及到虚拟机栈?
内存区块 | Error | GC |
---|---|---|
程序计数器 | ❌ | ❌ |
本地方法栈 | ✅ | ❌ |
jvm虚拟机栈 | ✅ | ❌ |
堆 | ✅ | ✅ |
方法区 | ✅ | ✅ |
5.方法中定义的局部变量是否线程安全?
/**
* 面试题:
* 方法中定义的局部变量是否线程安全?具体状况具体分析
*
* 何为线程安全?
* 若是只有一个线程能够操做此数据,则毙是线程安全的。
* 若是有多个线程操做此数据,则此数据是共享数据。若是不考虑同步机制的话,会存在线程安全问题
*
* StringBuffer是线程安全的,StringBuilder不是
*/
public class StringBuilderTest {
//s1的声明方式是线程安全的
public static void method1(){
StringBuilder s1 = new StringBuilder();
s1.append("a");
s1.append("b");
}
//stringBuilder的操做过程:是不安全的,由于method2能够被多个线程调用
public static void method2(StringBuilder stringBuilder){
stringBuilder.append("a");
stringBuilder.append("b");
}
//s1的操做:是线程不安全的 有返回值,可能被其余线程共享
public static StringBuilder method3(){
StringBuilder s1 = new StringBuilder();
s1.append("a");
s1.append("b");
return s1;
}
//s1的操做:是线程安全的 ,StringBuilder的toString方法是建立了一个新的String,s1在内部消亡了
public static String method4(){
StringBuilder s1 = new StringBuilder();
s1.append("a");
s1.append("b");
return s1.toString();
}
public static void main(String[] args) {
StringBuilder s = new StringBuilder();
new Thread(()->{
s.append("a");
s.append("b");
}).start();
method2(s);
}
}
复制代码
【代码】
github.com/willShuhuan…
【笔记】
JVM_01 简介
JVM_02 类加载子系统
JVM_03 运行时数据区1- [程序计数器+虚拟机栈+本地方法栈]
JVM_04 本地方法接口
JVM_05 运行时数据区2-堆
JVM_06 运行时数据区3-方法区
JVM_07 运行时数据区4-对象的实例化内存布局与访问定位+直接内存
JVM_08 执行引擎(Execution Engine)
JVM_09 字符串常量池StringTable
JVM_10 垃圾回收1-概述+相关算法
JVM_11 垃圾回收2-垃圾回收相关概念
JVM_12 垃圾回收3-垃圾回收器