Java虚拟机学习

这是本人学习Java虚拟机(JVM)的笔记

在Oracle的hotspot中将方法区成为non-heap,特意与堆区分开来。

方法区用于存放Class的相关信息,比如类名,访问修饰符,常量池,字段描述,方法描述等。

常量池:存于方法区
1 字符串常量池
2 类(class)常量池( class文件的表述信息,包含类和接口的全限定名; 字段名称和描述符; 方法名称和描述符 )
3 字面量常量池:Byte、Short、Integer、Long、Character、Boolean包装类的值在-128到127。
4 被final 所修饰的常量值

Java虚拟机的静态常量池和运行时常量池的区别

Java虚拟机中的数据存储区域包括(内存管理机制):
1java虚拟机栈
2本地方法栈
3堆
4程序计数器
5方法区:用来存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译之后的代码等数据。是各个线程共享的内存区域。(也可以叫永久代).在hotspot虚拟机中虚拟机栈和本地方法栈没有区别。

在这里插入图片描述

Out of memory 异常抛出: 当栈进行容量横向扩张(比如新建线程)时虚拟机没有足够的空间/堆没有足够的空间

(可达性分析算法)在java中,可作为GC Roots的对象包括:

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(一般说的Native方法)引用的对象

四种Java对象引用:

  • 强引用:平时创建的对象引用都为强引用。
  • 软引用:如果一个对象只具有软引用,如果java虚拟机内存空间足够,垃圾回收器就不会回收它,但当内存空间不足时,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
  • 弱引用:当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
  • 虚引用:“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象 仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队 列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

常见JVM垃圾回收算法

  • 标记-清除算法(最基础的):不足,标记和清除两个过程的效率都不高/清楚之后产生大量内存碎片。

  • 复制算法:实现简单,高效,清除之后无内存碎片,但是一次只能使用一半的内存。(一般作为回收新生代的垃圾收集算法,特点是对象存活率低,具体为将内存划分为一块较大的Eden和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时将Eden和Survivor还存活着的对象一次性复制到另外一块Survivor空间上。如果另外一块Survivor空间不够存放上一次新生代收集下来的存活对象,直接通过分配担保机制进入老年代)。

  • 标记-压缩算法:针对对象存活率较高的情况,标记对象,将存活的对象都向一端移动。适用于老年代。

Java虚拟机采用分代收集算法,年轻代一般采用复制算法,老年代一般采用标记-压缩(整理)算法(也有采用标记-清除算法,比如CMS)。
年轻代分为Eden和两个survivor,所以的新对象将存在Eden和一个survivor中,进行一次Minor GC时把Eden和survivor中的存活对象采用复制算法复制到另一个survivor中,如果第二个survivor空间不足,使用老年代作为担保。

方法区的垃圾回收(或者HotSpot虚拟机中的永久代))(回收性价比很低)主要的回收内容为:废弃的常量和无用的类。在大量使用反射,动态代理,CGLib等ByteCode框架,动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。
1、所有实例被回收
2、加载该类的ClassLoader被回收
3、Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类

并发(concurrency)和并行(parallellism)是:
解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间段间隔发生。(关键词:时刻,时间段)
解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
解释三:在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群采用并行的思想来加快数据处理。

JVM中的new一个新对象的内存分配过程:

  • 首先:使用逃逸分析技术,对于new的对象,首先分析如果该对象作用范围在方法内,即无逃逸,就在栈中为该对象分配内存。
  • 其次:TLAB(Thread-local allocation buffer)线程本地分配缓冲区。 每一个线程都有一个独自的小空间,用于存放新建的对象,不存在线程共享问题。如果对象大小合适,将在TLAB中为对象分配内存空间。
  • 再者:如果对象太大,无法在TLAB容纳,就在堆中分配内存。

垃圾收集器:

  • Serial收集器:单线程,stop the world。用于新生代,采用复制算法。
  • ParNew:Serial收集器的多线程版本, 收集垃圾时依旧stop the world。采用复制算法。
  • Parallel Scavenge收集器:针对新生代,采用复制算法,多线程,目标是达到一个可控的吞吐量。吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
  • Serial Old:是Serial收集器的老年版本,同样是一个单线程收集器,使用标记-压缩算法
  • Parallel Old: Parallel Scavenge收集器的老年版本,使用多线程和标记-压缩算法
  • CMS(Concurrent Mark Sweep):关注点是尽可能缩短垃圾收集时用户线程的停顿时间。垃圾收集线程和用户线程并发执行。顾名思义,使用标记-清除算法。使用于老年代。其所使用的标记清除算法注定了其所收集后的内存存在很多可用内存碎片。缺点:(1)对CPU资源非常敏感。(2)无法处理浮动垃圾(3)基于标记-清除算法,大量空间碎片。
  • G1(Garbage First)收集器是当今收集器计数发展最前沿成果之一。特别在低停顿方面。具体可以通过这个链接查看Oracle官方介绍,链接为:Garbage First。其中,如果满足下面三个条件,可以考虑使用G1收集器:(1)超过50%的Java堆空间被活动数据占用。(2)对象分配的速度差距较大.(3)不希望GC暂停时间超过0.5或1秒。有点(1)并发与并行, 充分利用多CPU的硬件优势(2)能独立管理整个GC堆,依然保留分代收集(3)整体来看是标记整理算法,而局部是复制算法。(4)可预测停顿。
  1. 新生代的复制算法建立在大部分对象朝生夕灭的特性上。2. “吞吐量优先”收集器:新生代用Parallel Scavenge,老年代用Parallel Old

两种不同的垃圾收集活动:

  • Minor GC, 新生代GC,指发生在新生代的垃圾收集动作。
  • Major GC/Full GC, 老年代GC,指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对)。Major GC的速度一般比Minor GC慢10倍以上。

垃圾回收的对象:(不能单纯地理解为“不可达的对象”)

  • 收集器从GC Roots中查找不可达地对象,宣布一次缓刑,将其加入F-Queue的队列中。
  • 稍后,虚拟机使用一个低优先级的线程执行F-Queue队列中对象的finalize方法,如果该对象没有自我拯救(成为可达对象),那么第二次扫描时该对象的内存将被回收。
  • 所有的对象的finalize方法只会被执行一次,所以一个对象只能自我拯救一次。

触发GC的条件:

  • 在程序中执行system.gc()。需要注意的是该方法只是建议JVM进行垃圾回收,不是一定会发生GC的。可通过虚拟机运行参数-XX:+DisableExplicitGC禁止该代码的运行。
  • 年轻代的Eden满了,触发Minor GC
  • 老年代满了,触发Full GC。一般地,Full GC之前先执行Minor GC。
  • 方法区也会被触发GC

空间分配担保:

每次Minor GC执行完以后,虚拟机会检查之前晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则发起一次Full/Major GC,如果小于,则查看HandlePromotionFailure值是否允许担保失败,如果允许,那只会进行一次Minor GC。如果不允许失败,那么也要改为进行一次Full/Major GC

-XX:+HandlePromotionFailure 是否允许新生代收集担保,进行一次minor gc后, 另一块Survivor空间不足时,将直接会在老年代中保留
-XX:GCTimeRatio:设置吞吐量大小,它的值是一个 0-100 之间的整数。假设 GCTimeRatio 的值为 n,那么系统将花费不超过 1/(1+n) 的时间用于垃圾收集。
-XX:MaxTenuringThreshold 晋升到老年代的对象年龄,每次Minor GC之后,年龄就加1,当超过这个参数的值时进入老年代
-XX:NewRatio 新生代(eden+2个survivor)和老年代的比值。

大对象直接进入老年代,目的是避免在Eden区及两个Survivor区之间发生大量的内存复制(新生代采用复制算法收集内存)

垃圾收集进行时,虚拟机虽然会对Direct Memory进行回收,但是Direct Memory却不能像新生代和老年代那样,发现空间不足了就通知收集器进行垃圾回收,它只能等待老年代满了之后Full GC,然后“顺便地”帮它清理掉内存地废弃对象,否则它只能一直等到抛出内存溢出异常时,先catch掉,再在catch块里面“System.gc()”。 要是虚拟机还是不听,比如打开了-XX:+DisableExplicitGC开关,那就只能抛出内存溢出异常了,即使虚拟机堆中还有许多空闲内存。

类加载过程:

  • 加载:(1)通过一个类的权限定名来获取定义此类的二进制字节流。(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。(3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口.

  • 链接

    • 认证:class字节码是否符合规范,是否安全
    • 准备:正式为类变量(仅仅包括类变量,即被static修饰的变量,不包含实例变量,实例变量将在对象实例化时随着对象一起分配在Java堆中)分配内存并设置类变量初始值,这些变量所使用的内存都将在方法区中进行分配。这里的初始值“通常”为零值,除非定义为:public static int value = 1243;这样就直接设置变量value的初始值为1243.
    • 解析:虚拟机将常量池内的符号引用变成直接引用的过程。(1.如果有了直接引用,那引用的目标必定已经在内存中存在。 2.常量池中的符号引用包含类和接口的信息常量,接口和类的方法信息常量,字段信息常量等)
  • 初始化:初始化阶段是执行类构造器()方法的过程。该方法是由编译器自动收集类中的所有类变量(即被static所修饰的变量)的赋值动作和静态语句块(static{})中的语句合并产生的,静态语句块中只能访问到定义在静态语句块之前的变量。该方法与类的构造函数(或者说实例构造器()方法)不同,它不需要显示地调用父类构造器,虚拟机会保证在子类的()方法已经执行之前,父类()方法已经执行完毕。(注意,该过程时类的初始化,不关对象的事情)

同一个class字节码文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等。

双亲委派模型:

  • Bootstrap ClassLoader: <JAVA_HOME>\lib或者被-Xbootclasspath参数所指定的路径中,并且是虚拟机识别的.
  • Extension ClassLoader: <JAVA_HOME>\lib\ext或者被java.ext.dirs,开发者可以直接使用扩展类加载器。
  • Application ClassLoader: 用户自定义的类加载器

包装类的==运算在不遇到算术运算的情况下不会自动拆箱,以及它们equals方法不处理数据转型的关系。在实际编码中尽量避免这样使用自动装箱和拆箱。如下面代码最后两个打印语句:

public class Test05 {
	public static void main(String[] args) {
		Integer a = 1;
		Integer b = 2;
		Integer c = 3;
		Integer d = 3;
		Integer e = 321;
		Integer f = 321;
		Long g = 3L;
		System.out.println(c == d);
		System.out.println(e == f);
		System.out.println(c == (a + b));
		System.out.println(c.equals(a+b));
		System.out.println(g == (a+b));
		System.out.println(g.equals(a+b));
	}
}
true
false
true
true
true
false

HotSpot虚拟机的解释器和即时编译器并存的原因:

  • 在程序启动的时候使用解释器,省去使用即时编译器编译的时间,在程序运行之后,即时编译器逐渐发挥作用。
  • 解释器相对即时编译器所占内存较小,两者是内存和效率的折中。
  • 解释器可以作为即时编译器激进优化时的一个“逃生门”,允许激进编译优化的回退和逆优化。

java内存模型:
1目的:消除不同硬件主内存和处理器缓存的差异性,Java虚拟机引入自身的内存模型。
2主内存,本地内存。每一个线程有一个独立的本地内存,其他线程无法访问。线程之间可以通过主内存来共享数据。这里就涉及到数据的可见性问题。使用同步锁和volatile可以实现数据的可见性问题。

参数名称说明
-Xms初始堆大小,物理内存的1/64(<1GB)
-Xmx最大堆大小,物理内存的1/4(<1GB)
-Xmn年轻代大小,此处的大小是(eden+ 2 survivor space)
-XX:PermSize设置持久代初始值,物理内存的1/64
-XX:MaxPermSize设置持久代最大值 物理内存的1/4
-Xss每个线程的堆栈大小,JDK1.5以后为1M
-XX:NewRatio年轻代(包括Eden和两个Survivor区)与年老代的比值

-XX:+CMSIncrementalMode垃圾收集器的CMS的增量模式这种模式已经被声明为“deprecated”(),不再提倡使用。
CMS的增量模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值