JDK、JRE、JVM之间的区别
-
JDK(Java SE Development Kit),java标准开发包,它提供了编译、运行Java程序所需的各种工具和资源,包括Java编译器、Java运行时环境,以及常用的Java类库等
-
JRE(Java Runtime Environment),java运行环境,用于运行Java的字节码文件。JRE中包括了JVM以及JVM所需要的类库,普通用户而只需安装JRE来运行Java程序,而程序开发者必须安装JDK来编译、调试程序。
-
JVM(Java Virtual Mechinal),Java虚拟机,是JRE的一部分,它是整个Java实现跨平台的最核心的部分,复杂运行字节码文件
我们写Java代码,用txt文本就可以写,想要运行,就需要先编译乘字节码,那就需要编译器,而JDK就包含了编译器javac,编译之后的字节码,想要运行就需要一个可以执行字节码的程序,这个程序就叫JVM(Java虚拟机),专门用于执行Java字节码的。
如果我们要开发Java程序,那就需要JDK,因为要编译Java源文件
如果我们只是想运行已经编译好的Java字节码文件,也就是*.class文件,那么就需要JRE
JDK中包含JRE,JRE中包含JVM
另外,JVM在执行Java字节码时,需要把字节码解释为机器指令,而不同操作系统的机械指令是有可能不一样的,所以导致不同操作系统上的JVM是不一样的,所以我们在安装JDK时需要选择操作系统
另外,JVM是用来执行Java字节码的,所以凡是某个代码编译之后是Java字节码,那就都能在JVM上运行,比如Apache Groovy Scala and Kotlin等等
完整的JDK体系
汇编语言要在不同平台输出helloworld,要开发不同的汇编代码
java虚拟机调优主要是调节内存区域
一般new出来的对象都存放在堆里 对象
栈(线程)放局部变量数据,只要一个线程一运行,Java虚拟机就会给它分配一个专属的内存空间,那么这个内存空间就叫做(线程栈)
(只要Java中有线程运行,虚拟机就分配一块内存,用于存放局部变量)
栈帧 一个方法对应一块栈帧内存区域
栈帧里除了局部变量 还有操作数栈 动态链接 方法出口
当方法调用一结束,内部的局部变量会销毁释放掉就是出栈 分配内存就是入栈
先调用的方法先分配内存,后调用的方法后分配内存 后调用的方法会先结束
javap -c xxx.java>xxx.txt 反编译
int a=1 = 0:iconst_1 1:iconst_1
程序计数器:每一个线程都独有的 值: 方法区那个内部的内存地址
Java开发人员为什么要设计一个程序计数器:当一个更高优先级的线程让它先执行,先头的线程就处于挂起,等待高优先级的线程运行完,再开始先头的线程,程序计数器就可以找到先头线程执行到哪一步再接着执行。
局部变量0相当于this
iload_1从局部变量1中装载int类型值 放在操作数栈里
iadd执行int类型的加法 会从操作数栈栈顶弹出最外边两个数做加法运算,再把结果压回栈
imul执行int类型的乘法
方法执行在栈中有内存地址,这个内存地址就放在动态链接里,需要调用方法就根据动态链接中的内存地址找方法区方法
方法出口 调用方法返回的信息都保存在方法出口 再根据储存信息知道返回main中哪一行代码继续执行
jdk8以后方法区改为元空间(常量+静态变量+类信息)
指针指向我们的堆 math,user是内存地址指向math,user 箭头黑色箭头指向
native本地方法 底层有实现,实现不是用Java写的,C++;本地方法栈也会分配一块内存给本地方法栈
new出来的对象优先是放在Eden区,当Eden区放不下了,Java虚拟机会让字节码执行引擎来 垃圾回收线程 minor gc
Java底层回收垃圾的算法 可达性分析算法
GC Roots根节点:线程栈的本地变量、静态变量、本地方法栈的变量等等
从Java虚拟机中寻找被引入用数据,从引用数据到最上层都标记为非垃圾 ,将这些引用数据全部加入survivor s0中,剩余大量对象在Eden中就会被销毁释放,minor gc会回收整个年轻代空间
(将“GC Roots”对象作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余未标记的对象就是垃圾对象)
当对象在s0s1中反复转换,每次minor gc都会加1,minor gc>15时,对象将被复制到老年代
Major Gc=young Gc,只回收老年代
这串代码会内存溢出,因为内存会不断被这个新new出来的对象填充,调用minor gc会发现,new出来的新对象被heapTests对象引用,heapTests又被ArrayList引用,所以这个对象无法被回收。
JDK自带调优工具jvisualvm,在cmd中输入该命令 重点看Visual GC查看当前运行中堆的变化
metaspace元空间 old老年代 Eden年轻代
Arthas调优工具 阿里巴巴Arthas Java诊断工具 支持JDK6+,采用命令行交互模式,可以方便的定义和诊断线上程序运行问题。
java虚拟机调优:减少gc 最核心减少full gc
当老年代当中对象放满了,不会立即销毁释放,会先执行full gc(通过字节码执行引擎执行full gc对整个堆进行垃圾回收)老年代装满了放不下了就会内存溢出(OOM)
当执行gc垃圾回收时,Java虚拟机会停止用户线程 STW(停止用户线程)
为什么设计STW机制,STW会影响用户体验
反证法
进行垃圾回收时,gc都在向下找引用对象,用户线程也在进行,这时候也大大影响性能,暂停用户线程,先进行垃圾回收,效果可能会更好。
调优:让生命周期非常短的对象,在年轻代就被回收,不要到老年代中调用fullgc
不同垃圾收集器底层停顿不一样
==和equals方法的区别
- ==:如果是基本数据类型,比较值,如果是引用类型,比较的是引用地址
- equals:具体看各个类重写equals方法之后的比较逻辑,比如string类,虽然是引用类型,但是string类重写了equals方法,方法内部比较的是字符串的各个字符是否全部相等
String s ="123";
s.equals("xxx");
进入equals内看源码
string类的equals
User user =new User();
user.equals()
进入equals内看源码
object类的equals方法
重载和重写的区别
- 重载(overload):在一个类中,同名的方法如果有不同参数列表(比如参数类型不同、参数个数不同)则视为重载
- 重写(override):从字面上看,重写就是重写写一遍的意思。其实就是在子类中把父类本身有的方法重新写一遍。子类继承父类的方法,但有时子类并不想原封不动的继承父类的某个方法,所以在方法名,参数列表,返回类型都相同(子类中方法的返回值可以是父类中返回的子类)的情况下,对方法进行修改,这就是重写,但要注意子类方法的访问修饰权限不能小于父类的
hashCode()与equals之间的关系
在Java中,每个对象都可以调用自己的hashCode方法得到自己的哈希值(hashcode),相当于对象的指纹信息,通常来说世界上没有完全相同的两个指纹,但是在Java中做不到这么绝对,但是我们人可以利用hashcode来做一些提前判断,比如:
-
如果两个对象的hashcode不相同,那么两个对象肯定不同的两个对象
-
如果两个对象的hashcode相同,不代表这两个对象一定是同一个对象,也可能是两个对象
-
如果两个对象相等,那么他们的hashcode就一定相同
在Java的一些集合类的实现中,在比较两个对象是否相等时,会根据上面的原则,会先调用对象的hashcode()方法得到hashcode进行比较,如果hashcode不同,就可以直接认为这两个对象不相同,如果hashcode相同,那么就会进一步调用equals()方法进行比较。而equals()方法,就是用来最终确定两个对象是不是相等,通常equals()方法的实现会比较重,逻辑比较多,而hashcode()主要就是得到一个哈希值,实际上就一个数字,相对而言比较轻,所以在比较两个对象时,通常就会优先根据hashcode相比较一下
所以我们就需要注意,如果我们重写了equals方法,那么就要注意hashcode方法,一定能保证能遵循上述规则。
得到两个user对象,去得到他们各自的name属性进行比较 如果两个对象name属性相等,那么这两个对象相等
结果为null
查看get方法,只有当两个对象的hashcode值相同时在调用equals方法
并发编程三要素
(1)原子性
原子性指的是一个或多个操作,要么全部执行并且在执行过程中不被其他操作打断,要么全部执行,要么全部不执行。
(2)可见性
可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果
(3)有序性
有序性,即程序的执行顺序按照代码的先后顺序来执行
实现可见性的方法有哪些
synchronized 或者Lock:保证同一个时刻只有线程获取锁执行代码,锁释放之前把最新的值刷新到主内存,实现可见性
多线程的价值
(1)发挥多核cpu的优势
多线程,可以真正发挥出多核cpu的优势来,达到充分利用cpu的目的,采用多线程的方式去同时完成几件事情而不互相干扰
(2)防止阻塞
从程序运行效率的角度来看,单核cpu不但不会发挥多线程的优势,反而会因为在单核cpu上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核cpu我们还是要应用多线程,就是为了防止阻塞。试想如果单核 CPU 使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行, 哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行
(3)便于建模
这是另外一个没有这么明显的优点了。假设有一个大的任务A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务分解成几个小任务,任务B,任务C,任务D,分别建立程序模型,并通过多线程分别运行这个几个任务,那么就会简单很多