(网上好心人给的md资料,发在csdn上方便学习,侵删)
Java体系结构包括四个独立但相关的技术:
我们再在看一下它们四者的关系:
当我们编写并运行一个Java程序时,就同时运用了这四种技术,用Java程序设计语言编写源代码,把它编译成Java.class文件格式,然后再在Java虚拟机中运行class文件。当程序运行的时候,它通过调用class文件实现了Java API的方法来满足程序的Java API调用
垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象占据的内存空间的一种机制。其体现在追踪所有正在使用的对象,并且将剩余的对象标记为垃圾,随后标记为垃圾的对象会被清除,回收这些垃圾对象占据的内存,从而实现内存的自动管理;
如果不进行垃圾回收,内存迟早都会被消耗完,垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存在C/C++中,释放无用变量内存空间的事情要由程序员自己来解决。Java有了GC,就不需要程序员去人工释放内存空间。当Java虚拟机发觉内存资源紧张的时候,就会自动地去清理无用变量所占用的内存空间
没有GC就不能保证应用程序的正常运行。而经常造成STW的GC又跟不上实际的需求,所以才会不断的进行GC优化。
这两篇博文对于JVM整体更为透彻:
存储在JVM的Java对象分为两类:
配置YoungGen和OldGen在堆结构的占比
默认 -XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3。
在HotSpot中,Eden空间和另外两个Survivor空间省所占比例是8:1:1。开发人员可以通过-XX:SurvivorRatio=8来调整这个空间比例。
-XX:-UseAdaptiveSizePlicy : 关闭自适应的内存分配策略。
几乎所有的Java对象都是在Eden区被new出来。
绝大部分的Java对象的销毁都是在新生代进行的。
可以使用"-Xmn"设置新生代最大内存大小,这个值一般默认就好了。
GC检索哪些是垃圾时,会导致用户线程暂停,所以希望GC出现的情况少,这里主要对Major GC、Full GC进行调优。因为它们两个GC的时间是Minor GC的10倍以上。
JVM在进行GC时,并非每次都对上面三个内存(新生代、老年代;方法区)区域一起回收,大部分时候回收的是新生代。
针对HotSpot VM的实现,他里面的GC按照回收区域分为两大类型:一种是部分收集(Partial GC),一种是整堆收集(Full GC)。
部分收集(Partial GC)
整堆收集(Full GC)
Java 堆主要分为2个区域-年轻代与老年代,其中年轻代又分 Eden 区和 Survivor 区,其中 Survivor 区又分 From 和 To 2个区
类是在运行期间第一次使用时动态加载的,而不是一次性加载所有类。因为如果一次性加载,那么会占用很多的内存。
加载(Loading) 验证(Verification) 准备(Preparation) 解析(Resolution)
初始化(Initialization) 使用(Using)卸载(Unloading)
如果要深入JVM推荐此博文:
JDK中提供了三个ClassLoader,根据层级从高到低为:
JVM加载类的实现方式,我们称为 双亲委托模型:
如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委托给自己的父加载器,每一层的类加载器都是如此,因此所有的类加载请求最终都应该传送到顶层的Bootstrap ClassLoader中,只有当父加载器反馈自己无法完成加载请求时,子加载器才会尝试自己加载。
Class<?> c = findLoadedClass(name);
双亲委托模型的重要用途是为了解决类载入过程中的安全性问题。 另一方面避免类重复字节码加载,节约内存
假设有一个开发者自己编写了一个名为***.lang.Object*的类,想借此欺骗JVM。现在他要使用自定义ClassLoader来加载自己编写的java.lang.Object类。然而幸运的是,双亲委托模型不会让他成功。因为JVM会优先在Bootstrap ClassLoader的路径下找到java.lang.Object**类,并载入它
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
//检查类是否被加载过
Class c = findLoadedClass(name);
if(c == null){
try{
if(parent != null){
c = parent.loadClass(name, false);
}else{
//父加载器为空默认使用启动类加载器
c = findBootstrapClassOrNull(name);
}
}catch (ClassNotFoundException e){
// 父类无法处理抛出ClassNotFoundException
}
if(c == null){
// 父类无法处理便调用本身findClass方法进行类加载
c = findClass(name);
}
}
if(resolve){
resolveClass(c);
}
return c;
}
JMM 定义了 8 个操作来完成主内存和工作内存之间的交互操作。JVM 实现时必须保证下面介绍的每种操作都是 原子的(对于 double 和 long 型的变量来说,load、store、read、和 write 操作在某些平台上允许有例外 )。
lock
(锁定) - 作用于主内存的变量,它把一个变量标识为一条线程独占的状态。unlock
(解锁) - 作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。read
(读取) - 作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load
动作使用。write
(写入) - 作用于主内存的变量,它把 store 操作从工作内存中得到的变量的值放入主内存的变量中。load
(载入) - 作用于工作内存的变量,它把 read 操作从主内存中得到的变量值放入工作内存的变量副本中。use
(使用) - 作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值得字节码指令时就会执行这个操作。assign
(赋值) - 作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。store
(存储) - 作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后 write
操作使用。volatile 禁止指令重排序(内存屏障) 以及保证可见性 具体可见第二章多线程常用方法volatile部分
原子性(Atomicity)
使用Java内存模型直接保证原子性变量包括read、load、assign、use、store和write六个,即大致可以认为基本数据类型的访问、读写都是具备原子性的;
虚拟机为了保证原子性,提供了两个高级的字节码指令 monitorenter
和 monitorexit
隐式使用lock与unlock操作。这两个字节码,在 Java 中对应的关键字就是 synchronized
。因此,在 Java 中可以使用 synchronized
来保证方法和代码块内的操作是原子性的。
可见性(Visibility)
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
JMM 是通过 "变量修改后将新值同步回主内存, 变量读取前从主内存刷新变量值" 这种依赖主内存作为传递媒介的方式来实现的。
Java 实现多线程可见性的方式有:
volatile
保证新值能立即同步到主内存,以及每次使用立即从主内存刷新synchronized
对一个变量执行unlock操作前必须将此变量同步到主内存中(执行store、write操作)final
被final修饰的字段一旦被初始化完成,且构造器没有把this的引用传递出去(this引用逃逸会导致其他线程访问到初始化一半的对象),那么在其他线程中就能看见final字段的值有序性(Ordering)
有序性规则表现在以下两种场景: 线程内和线程间
as-if-serial
)的方式执行,此种方式已经应用于顺序编程语言。synchronized
关键字修饰)以及 volatile
字段的操作仍维持相对有序。在 Java 中,可以使用 synchronized
和 volatile
来保证多线程之间操作的有序性。实现方式有所区别:
volatile
关键字会禁止指令重排序。synchronized
关键字通过互斥(lock)保证同一时刻只允许一条线程操作。红色区域线程私有不会出现线程竞争关系;蓝色区域线程共享,堆中存对象,方法区存类信息,常量,静态变量等;锁的主要应用范围是数据共享区
程序计数器(Program Counter Register)
线程私有,各条线程之间的计数器互不影响,用于在线程执行时充