Java随笔(一)

此篇博客主要是用来记录我之前存放在本地的Word文档中的一些Java的自身理解,由于水平有限所以可能只适合自己加深理解与记忆。


目录

垃圾回收中标记复制相关理解

对象进入老年代的例外情况

虚拟机空间分配担保策略

虚拟机性能监控工具

jps   

jstat

jinfo

jvisualvm 可以算是上面命令行一个大集合,我们可以使用它来进行简单的监控。

类生命周期相关

全局变量不赋值不会报错而局部变量不赋值使用报错原因

MethodHandle(JDK1.7提供的新型动态确定目标方法的机制)

使用MethodHandle和Reflection的区别


垃圾回收中标记复制相关理解

对象会优先在新生代生成,如果新生代的Eden剩余空间不足以放下,则会触发一次Minor GC (每次MinorGC都会将Eden中存在并能在Survivorto)中容纳的对象放到Survivorto,并将年岁增加一岁,Survivorfrom)中对象根据年龄,足够到老年代的存活对象放到老年代,其他能放入to的空间的放到to空间中,这样Eden和原来的Survivorfrom)都清空了,下次进行GC就会将上次GCto作为from而上次GCfrom(经过上次GC这个已经变为了空白空间)作为to来进行使用。)将Eden 和Survivor(from)复制到另外的Surivor(to)中,如果无法全部复制到to里面,则通过分配担保机制将所有新生代对象转移到老年代。如果设置了PretenureSizeThreshold  则 在创建对象的时候 超过这个设置值的对象不在Eden中分配空间,而直接在老年代中分配空间并创建,PretenureSizeThreshold单位不能以1MB这种方式,而是 1048576(即1024*1024)这种方式!

对象进入老年代的例外情况

一般我们认为对象的年龄要达到MaxTenuringThreshold之后,才会从新生代转移到老年代,实际上如果Survivor中 同年龄的 所有对象大小之和等于或大于Survivor的一半,年龄等于或大于该年龄对象就可以直接进入老年代。

虚拟机空间分配担保策略

发生MinorGC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果大于的话,那么MinorGC就是安全的,如果不成立,则虚拟机会查看是否允许HandlePromotionFailure设置值是否允许担保失败,如果允许,那么会检查老年代的最大连续空间是否大于历次晋升到老年代对象的平均大小,如果大于则进行一次MinorGC哪怕是有风险的,如果小于或者HandlePromotionFailure设置不允许冒险,则进行一次MajorGC(FullGC 也就是对老年代也进行GC操作 可能包含多次的MinorGC,可能使用标记-清除 或者 标记-整理 这要看收集器的选择,)。JDK 6 Update24之后,HandlePromotionFailure已经不会再影响虚拟机的空间分配担保策略,只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC否则进行Full GC。

FullGC速度很慢,可能比MinorGC慢10倍。

虚拟机性能监控工具

jps   

用处:可以列出正在运行的虚拟机进程

用法:jps [options] [hostid]

直接使用jps,会打印本机中运行的虚拟机进程。

jps可以通过RMI协议查询开启了RMI服务的远程虚拟机进程状态,hostid为RMI注册表中注册的主机名。

下面是在深入理解Java虚拟机一书中的参数截图:

Java随笔(一)_第1张图片

jstat

用处:用来监视虚拟机各种运行状态信息的命令行工具。

用法::jstat  [option vmid [ interval[s|ms] [count] ]]

参数 interval和count代表查询间隔和次数,如果省略这两个参数 则只查询1次,如:

jstat –gc 8788 200 20

表示 每200毫秒查询一次 进程8788的虚拟机的gc情况,一共查询20次。

options代表用户希望查询的虚拟机信息,主要是3类:类装载,垃圾收集,运行编译等,具体选项和作用见下面:

Java随笔(一)_第2张图片

下面是查询本机一个进程为8988的虚拟机的垃圾收集情况:

Java随笔(一)_第3张图片

分析如下:

S0  S1 分别新生代的 Survivor0和Survivor1

E                 代表Eden                                                                       使用了 0.59%

O                代表Old 老年代                                                               使用了 68.51%

P                代表Permanent 永久代                                                    使用了 31.83%

YGC          代表Minor GC(YGC代表Young GC)的次数                 475次

YGCT        代表Minor GC总耗时                                                        5.498s

FGC          代表Full GC的次数                                                           422次

FGCT        代表Full GC总耗时                                                           308.414s

GCT          代表所有GC操作总耗时                                                    313.913s

jinfo

用处:查看和调整虚拟机各项参数

用法:jinfo [options] pid

jvisualvm 可以算是上面命令行一个大集合,我们可以使用它来进行简单的监控。

类生命周期相关

基于jdk1.7(之后的版本可能也是这样,未验证),类加载到虚拟机内存开始到卸载出内存为止,经历的生命周期是:加载   验证  准备  解析  初始化 使用 卸载,如下图:

Java随笔(一)_第4张图片

其中 加载 验证 准备  初始化 卸载 5个阶段顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始。而解析阶段不一定什么时候开始,在某些情况可能在初始化后开始的。加载 什么时候开始,Java虚拟机规范中没有限定,这个        是由虚拟机具体实现就可以。 规定了 有且只有以下5种情况(对类的主动引用才会进行初始化。

  1. 通过new,getstatic,putstatic或者invokestatic这4条字节码指令时,如果类没有进行初始化会先进行初始化。生成这4条字节码最常见的场景是:使用new关键字实例化实例对象,读取或设置一个静态字段(final修饰,已在编译期把结果放到常量池的静态字段除外)以及调用一个静态方法时
  2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有初始化,则会先触发其初始化。
  3. 在初始化一个类的时候,发现父类还没被初始化,则需要先触发父类的初始化。
  4. 在虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个类。
  5. 当使用jdk1.7的动态语言支持的时候,如果一个java.lang.invokMethodHandle实例最后解析结果是REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,并且这个方法句柄对应的类未被初始化,则需要先触发其初始化。

以下3被动引用,不会触发类的初始化:

1. 通过子类引用父类的静态字段,不会引起子类的初始化(父类会被初始化)

package com.jvm;
public class SuperClass {
	static{
		System.out.println("SuperClass init");
	}
	public static int value = 123;
}

---------------------------------------------------------
package com.jvm;
public class SubClass extends SuperClass{
	static{
		System.out.println("SubClass init!");
	}
}
---------------------------------------------------------
package com.jvm;
public class NotInitialization {
	public static void main(String[] args) {
		System.out.println(SubClass.value);
	}
}

运行上述代码,发现输出SuperClass init!   以及 123,说明没有初始化SubClass!但是由于引用了SuperClass的非final静态变量,所以SuperClass被初始化了。

对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类的静态字段,只会触发父类的初始化而不会触发子类的初始化。而是否触发子类的加载和验证,虚拟机规范中并未明确规定,这点取决于虚拟机的具体实现,对于Sun HotSpot虚拟机来说,可以通过-XX:+TraceClassLoading(开启之后可以在控制台看到类型的加载信息)参数观察这个操作会导致子类的加载。

2.通过数组定义引用类,不会触发此类的初始化

package com.jvm;

public class NotInitialization {
	public static void main(String[] args) {
		SuperClass[] sca = new SuperClass[10];
	}
}

运行后不会输出SuperClass,说明没触发Superclass的初始化,但是这段代码会触发[LSuperClass  的类的初始化,它是由虚拟机自动生成的,直接继承java.lang.Object的子类,创建动作由字节码newarray触发。

3. 当访问类中的静态常量时候,不会触发类的初始化.这是由于常量在编译阶段会存入到调用类的常量池中,本质并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。

package com.jvm;

public class ConstClass {
	static{
		System.out.println("ConstClass init!");
	}
	public static final String HELLOWORLD = "hello world";
}

---------------------------------------------------------
package com.jvm;

public class NotInitialization {
	public static void main(String[] args) {
		System.out.println(ConstClass.HELLOWORLD);
	}
}

最后发现输出了 hello world  但是没有输出 “ConstClass init!”这是由于在编译阶段,NotInitialization对常量的引用实际转为了NotInitialization自身常量池的引用,也就是实际上NotInitialization的class并没有ConstClass类的符号引用入口!这两个类在编译成Class之后就不存在任何联系了!!!

由于不是很理解,于是做了如下试验,ConstClass 开始HELLOWORLD 设置为hello world了。然后NotInitialization只是使用了这个静态常量,然后将NotInitialization编译的class单独拷出来,然后使用java运行,发现可以正常运行!!这从侧面验证了,引用的静态常量被保存在调用类的常量池中了。另外试过 修改ConstClass 的HELLOWORLD设置为hello world 33.将ConstClass新编译的class放到原来NotInitialization.class同目录,执行java NotInitialization 发现输出依然是hello world!!!这就说明 确实可以认为 这种 A类仅仅引用B类中静态常量的方式,在编译期,A类会将这个常量放到自己常量池中,并将对B类的常量的引用转为对自身类中的常量池的引用,所以需要注意这种引用其他类的常量的类,如果常量发生改变,需要将调用类 A类也重新编译 覆盖使用(单从使用来讲,只需要覆盖编译后A类就能起到引用的常量的改变。)如果使用loadClass这种方式 也是不会触发类的初始化的,这也要注意!!!

接口和类的初始化过程,有区别的是前面讲述的5 “有且仅且”需要开始初始化的场景的第3种:当一个类初始化的时候,要其父类全部已经初始化了,但是一个接口初始化   时,并不要求其父接口全部完成初始化,只有真正使用到父接口(如引用接口中定义的常量)才会初始化。

调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。

全局变量不赋值不会报错而局部变量不赋值使用报错原因

在Java里面,我们知道全局(类)变量可以不设置默认值,然后直接在方法里面使用,但是如果是局部变量,如果不设置默认值,直接使用的话会报错,这是因为 对于全局(类)变量,有两次赋初始值的过程,第一次是 在类的 准备阶段,赋系统初始值,另外一次是在初始化阶段,赋予程序员定义的初始值。因此,程序员不为类变量赋值也没关系,类变量仍然有一个具体的初始值。但是局部变量就不一样,如果局部变量定义了但是没赋初始值是不能使用的。

比如下图代码:

Java随笔(一)_第5张图片

发现 在编译的时候会报错

因为局部变量b未初始化就使用了,而全局变量同样没有在代码中初始化赋值 却没有报错。

MethodHandle(JDK1.7提供的新型动态确定目标方法的机制)

MethodHandle为我们提供了除反射外的另外一种动态确定执行方法的方式,如下代码根据执行方法时候的时间来执行不同对象的方法:

package com.jvm;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

public class MethodHandleTest {
	static class ClassA{
		public void println(String a){
			System.out.println(a+"来自ClassA");
		}
	}
	public static void main(String[] args) throws Throwable {
		Object obj = System.currentTimeMillis() % 2 == 0 ? System.out : new ClassA();
		getPrintObj(obj).invokeExact("mytest");
	}
	public static MethodHandle getPrintObj(Object reveiver) 
			throws Throwable{
		MethodType mt = MethodType.methodType(void.class,String.class);
		return MethodHandles.lookup()
				.findVirtual(reveiver.getClass(), "println", mt).bindTo(reveiver);
	}
}

这里用到了MethodHandle这个类,然后动态调用不同类的println方法。代码解释:main方法中,首先要定义 执行这个方法的类的对象,这里是随机生成的两个对象,然后自定义的方法 getPrintObj 模拟字节码中invokevirtual指令的执行过程,只是它的分派逻辑不是固化在Class文件的字节码中,而是通过具体方法实现,这个方法的返回值  MethodHandle可以认为是对最终调用方法的一个引用。

MethodType 代表”方法类型”,包含了方法的返回值类型(methodType的第一个参数)以及具体参数类型(methodType的第二个以及后面的参数),MethodHandles.lookup()  这句是在指定类中查找符合给定方法名称,方法类型,并且符合调用权限的方法句柄,因为这里调用的是一个虚方法,按照Java语言的规划,方法第一个参数是隐式的,代表该方法的接受者(可以理解为使用者?)也即是this指向的对象,这个参数以前是放在参数列表中传递,现在提供了bindTo来传递!

使用MethodHandle和Reflection的区别

最重要的是Reflection的设计目标只为Java语言服务,而MethodHandle设计成可服务于所有Java虚拟机以上的语言,也包括Java语言。

Reflection是重量级的,而MethodHandle是轻量级的。

 

你可能感兴趣的:(Java,Java,Java随笔)