spring Assert教程(转)

Assert类目的 Spring Assert类帮助我们校验参数。通过使用Assert类方法,我们可以写出我们认为是正确的假设,反之,会抛出运行时异常。 每个Assert的方法可以与java assert表达式进行比较。java assert表达式在运行时如果条件校验失败,则抛出Error,有趣的是,这些断言可以被禁用。 Spring Assert的方法有一些特点: 都是static方法 抛出IllegalArgumentException 或 IllegalStateException异常 第一个参数通常是需验证的对象或逻辑条件 最后参数通常是异常消息,用于验证失败时显示 消息可以作为String参数或Supplier 参数传输 尽管Spring Assert与其他框架的名称类似,如JUnit或其他框架,但其实没有任何共同之处。Spring Assert不是为了测试,而是为了调试。 使用示例 让我们定义Car类,并有public方法drive(): 我们看到speed必须是正数,上面一行简短的代码用于检测条件,如果失败抛出异常: 逻辑断言 isTrue() 上面已经看到示例,其接受布尔条件,如果条件为假抛出IllegalArgumentException 异常。 state() 该方法与isTrue一样,但抛出IllegalStateException异常。 如名称所示,通常用在因对象的非法状态时,方法不能继续执行。假设骑车运行是不能加油,我们可以使用state方法断言: 当然,我们能使用逻辑断言验证所有场景。但为了更好的可读性,我们可以使用其他的断言,使代码表达性更好。 对象和类型断言 notNull() 通过notNull()方法可以假设对象不null: isNull() 另外一方面,我们能使用isNull()方法检查对象为null: isInstanceOf() 使用isInstanceOf()方法检查对象必须为另一个特定类型的实例: 示例中,ToyotaEngine 是类 Engine的子类,所以检查通过. isAssignable() 使用Assert.isAssignable()方法检查类型: 这两个断言代表 is-a 关系. 文本断言 通常用来检查字符串参数。 hasLength() 如果检查字符串不是空符串,意味着至少包含一个空白,可以使用hasLength()方法: hasText() 我们能增强检查条件,字符串至少包含一个非空白字符,可以使用hasText()方法: doesNotContain() 我们能通过doesNotContain()方法检查参数不包含特定子串: Collection和map断言 Collection应用notEmpty() […]

新一代垃圾回收器ZGC的探索与实践

转:https://mp.weixin.qq.com/s/ag5u2EPObx7bZr7hkcrOTg 很多低延迟高可用Java服务的系统可用性经常受GC停顿的困扰,作为新一代的低延迟垃圾回收器,ZGC在大内存低延迟服务的内存管理和回收方面,有着非常不错的表现。 本文从GC之痛、ZGC原理、ZGC调优实践、升级ZGC效果等维度展开,详述了ZGC在美团低延时场景中的应用,以及在生产环境中取得的一些成果。希望这些实践对大家有所帮助或者启发。 ZGC(The Z Garbage Collector)是JDK 11中推出的一款低延迟垃圾回收器,它的设计目标包括: 停顿时间不超过10ms; 停顿时间不会随着堆的大小,或者活跃对象的大小而增加; 支持8MB~4TB级别的堆(未来支持16TB)。 从设计目标来看,我们知道ZGC适用于大内存低延迟服务的内存管理和回收。本文主要介绍ZGC在低延时场景中的应用和卓越表现,文章内容主要分为四部分: GC之痛:介绍实际业务中遇到的GC痛点,并分析CMS收集器和G1收集器停顿时间瓶颈; ZGC原理:分析ZGC停顿时间比G1或CMS更短的本质原因,以及背后的技术原理; ZGC调优实践:重点分享对ZGC调优的理解,并分析若干个实际调优案例; 升级ZGC效果:展示在生产环境应用ZGC取得的效果。 GC之痛 很多低延迟高可用Java服务的系统可用性经常受GC停顿的困扰。GC停顿指垃圾回收期间STW(Stop The World),当STW时,所有应用线程停止活动,等待GC停顿结束。 以美团风控服务为例,部分上游业务要求风控服务65ms内返回结果,并且可用性要达到99.99%。但因为GC停顿,我们未能达到上述可用性目标。当时使用的是CMS垃圾回收器,单次Young GC 40ms,一分钟10次,接口平均响应时间30ms。通过计算可知,有( 40ms + 30ms ) * 10次 / 60000ms = 1.12%的请求的响应时间会增加0 ~ 40ms不等,其中30ms * 10次 / 60000ms = 0.5%的请求响应时间会增加40ms。 可见,GC停顿对响应时间的影响较大。为了降低GC停顿对系统可用性的影响,我们从降低单次GC时间和降低GC频率两个角度出发进行了调优,还测试过G1垃圾回收器,但这三项措施均未能降低GC对服务可用性的影响。 CMS与G1停顿时间瓶颈 在介绍ZGC之前,首先回顾一下CMS和G1的GC过程以及停顿时间的瓶颈。CMS新生代的Young GC、G1和ZGC都基于标记-复制算法,但算法具体实现的不同就导致了巨大的性能差异。 标记-复制算法应用在CMS新生代(ParNew是CMS默认的新生代垃圾回收器)和G1垃圾回收器中。标记-复制算法可以分为三个阶段: 标记阶段,即从GC Roots集合开始,标记活跃对象; 转移阶段,即把活跃对象复制到新的内存地址上; 重定位阶段,因为转移导致对象的地址发生了变化,在重定位阶段,所有指向对象旧地址的指针都要调整到对象新的地址上。 下面以G1为例,通过G1中标记-复制算法过程(G1的Young GC和Mixed GC均采用该算法),分析G1停顿耗时的主要瓶颈。G1垃圾回收周期如下图所示: G1的混合回收过程可以分为标记阶段、清理阶段和复制阶段。 标记阶段停顿分析 […]

Java字节码增强探秘

转自 1. 字节码 1.1 什么是字节码? Java之所以可以“一次编译,到处运行”,一是因为JVM针对各种操作系统、平台都进行了定制,二是因为无论在什么平台,都可以编译生成固定格式的字节码(.class文件)供JVM使用。因此,也可以看出字节码对于Java生态的重要性。之所以被称之为字节码,是因为字节码文件由十六进制值组成,而JVM以两个十六进制值为一组,即以字节为单位进行读取。在Java中一般是用javac命令编译源代码为字节码文件,一个.java文件从编译到运行的示例如图1所示。 图1 Java运行示意图 对于开发人员,了解字节码可以更准确、直观地理解Java语言中更深层次的东西,比如通过字节码,可以很直观地看到Volatile关键字如何在字节码上生效。另外,字节码增强技术在Spring AOP、各种ORM框架、热部署中的应用屡见不鲜,深入理解其原理对于我们来说大有裨益。除此之外,由于JVM规范的存在,只要最终可以生成符合规范的字节码就可以在JVM上运行,因此这就给了各种运行在JVM上的语言(如Scala、Groovy、Kotlin)一种契机,可以扩展Java所没有的特性或者实现各种语法糖。理解字节码后再学习这些语言,可以“逆流而上”,从字节码视角看它的设计思路,学习起来也“易如反掌”。 本文重点着眼于字节码增强技术,从字节码开始逐层向上,由JVM字节码操作集合到Java中操作字节码的框架,再到我们熟悉的各类框架原理及应用,也都会一一进行介绍。 1.2 字节码结构 .java文件通过javac编译后将得到一个.class文件,比如编写一个简单的ByteCodeDemo类,如下图2的左侧部分: 图2 示例代码(左侧)及对应的字节码(右侧) 编译后生成ByteCodeDemo.class文件,打开后是一堆十六进制数,按字节为单位进行分割后展示如图2右侧部分所示。上文提及过,JVM对于字节码是有规范要求的,那么看似杂乱的十六进制符合什么结构呢?JVM规范要求每一个字节码文件都要由十部分按照固定的顺序组成,整体结构如图3所示。接下来我们将一一介绍这十个部分: 图3 JVM规定的字节码结构 (1) 魔数(Magic Number) 所有的.class文件的前四个字节都是魔数,魔数的固定值为:0xCAFEBABE。魔数放在文件开头,JVM可以根据文件的开头来判断这个文件是否可能是一个.class文件,如果是,才会继续进行之后的操作。 有趣的是,魔数的固定值是Java之父James Gosling制定的,为CafeBabe(咖啡宝贝),而Java的图标为一杯咖啡。 (2) 版本号 版本号为魔数之后的4个字节,前两个字节表示次版本号(Minor Version),后两个字节表示主版本号(Major Version)。上图2中版本号为“00 00 00 34”,次版本号转化为十进制为0,主版本号转化为十进制为52,在Oracle官网中查询序号52对应的主版本号为1.8,所以编译该文件的Java版本号为1.8.0。 (3) 常量池(Constant Pool) 紧接着主版本号之后的字节为常量池入口。常量池中存储两类常量:字面量与符号引用。字面量为代码中声明为Final的常量值,符号引用如类和接口的全局限定名、字段的名称和描述符、方法的名称和描述符。常量池整体上分为两部分:常量池计数器以及常量池数据区,如下图4所示。 图4 常量池的结构 常量池计数器(constant_pool_count):由于常量的数量不固定,所以需要先放置两个字节来表示常量池容量计数值。图2中示例代码的字节码前10个字节如下图5所示,将十六进制的24转化为十进制值为36,排除掉下标“0”,也就是说,这个类文件中共有35个常量。 图5 前十个字节及含义 常量池数据区:数据区是由(constant_pool_count-1)个cp_info结构组成,一个cp_info结构对应一个常量。在字节码中共有14种类型的cp_info(如下图6所示),每种类型的结构都是固定的。 图6 各类型的cp_info 具体以CONSTANT_utf8_info为例,它的结构如下图7左侧所示。首先一个字节“tag”,它的值取自上图6中对应项的Tag,由于它的类型是utf8_info,所以值为“01”。接下来两个字节标识该字符串的长度Length,然后Length个字节为这个字符串具体的值。从图2中的字节码摘取一个cp_info结构,如下图7右侧所示。将它翻译过来后,其含义为:该常量类型为utf8字符串,长度为一字节,数据为“a”。 图7 CONSTANT_utf8_info的结构(左)及示例(右) 其他类型的cp_info结构在本文不再赘述,整体结构大同小异,都是先通过Tag来标识类型,然后后续n个字节来描述长度和(或)数据。先知其所以然,以后可以通过javap -verbose ByteCodeDemo命令,查看JVM反编译后的完整常量池,如下图8所示。可以看到反编译结果将每一个cp_info结构的类型和值都很明确地呈现了出来。 图8 常量池反编译结果 (4) 访问标志 […]

Java线程池实现原理

转:https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651751537&idx=1&sn=c50a434302cc06797828782970da190e&chksm=bd125d3c8a65d42aaf58999c89b6a4749f092441335f3c96067d2d361b9af69ad4ff1b73504c&scene=21#wechat_redirect 主体是了解原理。及关于动态线程池的一个思路。并且说明线程池的部分参数是支持动态设置的。 随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流。使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器。J.U.C提供的线程池ThreadPoolExecutor类,帮助开发人员管理线程并方便地执行并行任务。了解并合理使用线程池,是一个开发人员必修的基本功。 本文开篇简述线程池概念和用途,接着结合线程池的源码,帮助读者领略线程池的设计思路,最后回归实践,通过案例讲述使用线程池遇到的问题,并给出了一种动态化线程池解决方案。 一、写在前面 1.1 线程池是什么 线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服务器中,如MySQL。 线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等,同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。 而本文描述线程池是JDK中提供的ThreadPoolExecutor类。 当然,使用线程池可以带来一系列好处: 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。 提高响应速度:任务到达时,无需等待线程创建即可立即执行。 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。 1.2 线程池解决的问题是什么 线程池解决的核心问题就是资源管理问题。在并发环境下,系统不能够确定在任意时刻中,有多少任务需要执行,有多少资源需要投入。这种不确定性将带来以下若干问题: 频繁申请/销毁资源和调度资源,将带来额外的消耗,可能会非常巨大。 对资源无限申请缺少抑制手段,易引发系统资源耗尽的风险。 系统无法合理管理内部的资源分布,会降低系统的稳定性。 为解决资源分配这个问题,线程池采用了“池化”(Pooling)思想。池化,顾名思义,是为了最大化收益并最小化风险,而将资源统一在一起管理的一种思想。 Pooling is the grouping together of resources (assets, equipment, personnel, effort, etc.) for the purposes of maximizing advantage or minimizing risk to the users. The term is used in finance, computing and […]

如何不重启JVM,替换掉已经加载的类

https://mp.weixin.qq.com/s/5o5XcqbZh5jRq-zcza5_Hw 如果项目有开放Debug端口,可以通过远程Debug来排查部分问题。 # Java对象行为 问题本质上是动态改变内存中已存在对象的行为问题。 所以,得先弄清楚JVM中和对象行为有关的地方在哪里,有没有更改的可能性。 对象使用两种东西来描述事物:行为和属性。 举个例子: 上面Person类中age和name是属性,speak是行为。对象是类的实例,每个对象的属性都属于对象本身,但是每个对象的行为却是公共的。举个例子,比如我们现在基于Person类创建了两个对象,personA和personB: personA和personB有各自的姓名和年龄,但是有共同的行为:speak。想象一下,如果我们是Java语言的设计者,我们会怎么存储对象的行为和属性呢? “很简单,属性跟着对象走,每个对象都存一份。行为是公共的东西,抽离出来,单独放到一个地方。” “咦?抽离出公共的部分,跟代码复用好像啊。” “大道至简,很多东西本来都是殊途同归。” 也就是说,第一步我们首先得找到存储对象行为的这个公共的地方。一番搜索之后,我们发现这样一段描述: Method area is created on virtual machine startup, shared among all Java virtual machine threads and it is logically part of heap area. It stores per-class structures such as the run-time constant pool, field and method data, and the code for […]

Spring 注解式 AOP 的底层实现原理

转:https://mp.weixin.qq.com/s/I2SlX0Ud9Xze2X92bfgEtg IoC 和 AOP 被称为 Spring 两大基础模块,支撑着上层扩展的实现和运行。虽然 AOP 同样建立在 IoC 的实现基础之上,但是作为对 OOP(Object-Oriented Programing) 的补充,AOP(Aspect-Oriented Programming) 在程序设计领域拥有其不可替代的适用场景和地位。Spring AOP 作为 AOP 思想的实现,被誉为 Spring 框架的基础模块也算是实至名归。Spring 在 1.0 版本的时候就引入了对 AOP 的支持,并且随着版本的迭代逐渐提供了基于 XML 配置、注解,以及 schema 配置的使用方式,考虑到实际开发中使用注解配置的方式相对较多,所以本文主要分析注解式 AOP 的实现和运行机制。 # 注解式 AOP 示例 **** 首先,我们还是通过一个简单的示例演示一下注解式 AOP 的具体使用。假设我们声明了一个 IService 接口,并提供了相应的实现类 ServiceImpl,如下: 现在我们希望借助 Spring AOP 实现对方法调用的打点功能。首先我们需要定义一个切面: 通过 @Aspect 注解标记 MetricAspect 是一个切面,通过注解 @Before、@After,以及 @Around,我们在切面中定义了相应的前置、后置,以及环绕增强。然后我们需要在 […]

Spring Boot @Enable*注解源码解析及自定义@Enable*(转)

Spring Boot 一个重要的特点就是自动配置,约定大于配置,几乎所有组件使用其本身约定好的默认配置就可以使用,大大减轻配置的麻烦。其实现自动配置一个方式就是使用@Enable*注解,见其名知其意也,即“使什么可用或开启什么的支持”。 Spring Boot 常用@Enable* 首先来简单介绍一下Spring Boot 常用的@Enable*注解及其作用吧。 @EnableAutoConfiguration 开启自动扫描装配Bean,组合成@SpringBootApplication注解之一 @EnableTransactionManagement 开启注解式事务的支持。 @EnableCaching 开启注解式的缓存支持。 @Enable*的源码解析 @EnableAutoConfiguration 源码规律及解析可以发现它们都使用了@Import注解(其中@Target:注解的作用目标,@Retention:注解的保留位置,@Inherited:说明子类可以继承父类中的该注解,@Document:说明该注解将被包含在javadoc中) 该元注解是被用来整合所有在@Configuration注解中定义的bean配置,即相当于我们将多个XML配置文件导入到单个文件的情形。 而它们所引入的配置类,主要分为Selector和Registrar,其分别实现了ImportSelector和ImportBeanDefinitionRegistrar接口, 两个的大概意思都是说,会根据AnnotationMetadata元数据注册bean类,即返回的Bean 会自动的被注入,被Spring所管理。 既然他们功能都相同,都是用来返回类,为什么 Spring 有这两种不同的接口类的呢? 首先我们从上面截图可以看到ImportBeanDefinitionRegistrar接口类中 registerBeanDefinitions方法多了一个参数 BeanDefinitionRegistry(点击这个参数进入看这个参数的Javadoc,可以知道,它是用于保存bean定义的注册表的接口),所以如果是实现了这个接口类的首先可以应用比较复杂的注册类的判断条件,例如:可以判断之前的类是否有注册到 Spring 中了。另外就是实现了这个接口类能修改或新增 Spring 类定义BeanDefinition的一些属性(查看其中一个实现了这个接口例子如:AspectJAutoProxyRegistrar,追查 BeanDefinitionRegistry参数可以查看到)。 源码小结通过查看@Enable*源码,我们可以清楚知道其实现自动配置的方式的底层就是通过@Import注解引入相关配置类,然后再在配置类将所需的bean注册到spring容器中和实现组件相关处理逻辑去。 自定义@Enable*注解(EnableSelfBean)   在这里我们利用@Import和ImportSelector动手自定义一个自己的EnableSelfBean。该Enable注解可以将某些包下的所有类自动注册到spring容器中,对于一些实体类的项目很多的情况下,可以考虑一下通过这种方式将某包下所有类自动加入到spring容器,不再需要每个类再加上@Component等注解。 先创建一个spring boot项目。创建包entity,并新建类Role,将其放入到entity包中。 创建自定义配置类SelfEnableAutoConfig并实现ImportSelector接口。其中使用到ClassUtils类是用来获取自己某个包下的所有类的名称的。 ClassUtil类 创建自定义注解类EnableSelfBean 创建启动类SpringBootEnableApplication 启动类测试的一些感悟:重新复习了回了spring的一些基础东西,如: @Autowired是默认通过by type(即类对象)得到注册的类,如果有多个实现才使用by name来确定。所有注册的类的信息存储在ApplicationContext中,可以通过ApplicationContext得到注册类,Spring boot中如果@ComponentScan没有,则默认是指扫描当前启动类所在的包里的对象。

Java双刃剑之Unsafe类详解(转)

前一段时间在研究juc源码的时候,发现在很多工具类中都调用了一个Unsafe类中的方法,出于好奇就想要研究一下这个类到底有什么作用,于是先查阅了一些资料,一查不要紧,很多资料中对Unsafe的态度都是这样的画风: 其实看到这些说法也没什么意外,毕竟Unsafe这个词直译过来就是“不安全的”,从名字里我们也大概能看来Java的开发者们对它有些不放心。但是作为一名极客,不能你说不安全我就不去研究了,毕竟只有了解一项技术的风险点,才能更好的避免出现这些问题嘛。 下面我们言归正传,先通过简单的介绍来对Unsafe类有一个大致的了解。Unsafe类是一个位于sun.misc包下的类,它提供了一些相对底层方法,能够让我们接触到一些更接近操作系统底层的资源,如系统的内存资源、cpu指令等。而通过这些方法,我们能够完成一些普通方法无法实现的功能,例如直接使用偏移地址操作对象、数组等等。但是在使用这些方法提供的便利的同时,也存在一些潜在的安全因素,例如对内存的错误操作可能会引起内存泄漏,严重时甚至可能引起jvm崩溃。因此在使用Unsafe前,我们必须要了解它的工作原理与各方法的应用场景,并且在此基础上仍需要非常谨慎的操作,下面我们正式开始对Unsafe的学习。 Unsafe 基础 首先我们来尝试获取一个Unsafe实例,如果按照new的方式去创建对象,不好意思,编译器会报错提示你: 查看Unsafe类的源码,可以看到它被final修饰不允许被继承,并且构造函数为private类型,即不允许我们手动调用构造方法进行实例化,只有在static静态代码块中,以单例的方式初始化了一个Unsafe对象: 在Unsafe类中,提供了一个静态方法getUnsafe,看上去貌似可以用它来获取Unsafe实例: 但是如果我们直接调用这个静态方法,会抛出异常: 这是因为在getUnsafe方法中,会对调用者的classLoader进行检查,判断当前类是否由Bootstrap classLoader加载,如果不是的话那么就会抛出一个SecurityException异常。也就是说,只有启动类加载器加载的类才能够调用Unsafe类中的方法,来防止这些方法在不可信的代码中被调用。 那么,为什么要对Unsafe类进行这么谨慎的使用限制呢,说到底,还是因为它实现的功能过于底层,例如直接进行内存操作、绕过jvm的安全检查创建对象等等,概括的来说,Unsafe类实现功能可以被分为下面8类: 创建实例 看到上面的这些功能,你是不是已经有些迫不及待想要试一试了。那么如果我们执意想要在自己的代码中调用Unsafe类的方法,应该怎么获取一个它的实例对象呢,答案是利用反射获得Unsafe类中已经实例化完成的单例对象: 在获取到Unsafe的实例对象后,我们就可以使用它为所欲为了,先来尝试使用它对一个对象的属性进行读写: 运行代码输出如下,可以看到通过Unsafe类的objectFieldOffset方法获取了对象中字段的偏移地址,这个偏移地址不是内存中的绝对地址而是一个相对地址,之后再通过这个偏移地址对int类型字段的属性值进行了读写操作,通过结果也可以看到Unsafe的方法和类中的get方法获取到的值是相同的。 在上面的例子中调用了Unsafe类的putInt和getInt方法,看一下源码中的方法: 先说作用,getInt用于从对象的指定偏移地址处读取一个int,putInt用于在对象指定偏移地址处写入一个int,并且即使类中的这个属性是private私有类型的,也可以对它进行读写。但是有细心的小伙伴可能发现了,这两个方法相对于我们平常写的普通方法,多了一个native关键字修饰,并且没有具体的方法逻辑,那么它是怎么实现的呢? native方法 在java中,这类方法被称为native方法(Native Method),简单的说就是由java调用非java代码的接口,被调用的方法是由非java 语言实现的,例如它可以由C或C++语言来实现,并编译成DLL,然后直接供java进行调用。native方法是通过JNI(Java Native Interface)实现调用的,从 java1.1开始 JNI 标准就是java平台的一部分,它允许java代码和其他语言的代码进行交互。 Unsafe类中的很多基础方法都属于native方法,那么为什么要使用native方法呢?原因可以概括为以下几点: 需要用到 java 中不具备的依赖于操作系统的特性,java在实现跨平台的同时要实现对底层的控制,需要借助其他语言发挥作用 对于其他语言已经完成的一些现成功能,可以使用java直接调用 程序对时间敏感或对性能要求非常高时,有必要使用更加底层的语言,例如C/C++甚至是汇编 在juc包的很多并发工具类在实现并发机制时,都调用了native方法,通过它们打破了java运行时的界限,能够接触到操作系统底层的某些功能。对于同一个native方法,不同的操作系统可能会通过不同的方式来实现,但是对于使用者来说是透明的,最终都会得到相同的结果,至于java如何实现的通过JNI调用其他语言的代码,不是本文的重点,会在后续的文章中具体学习。 Unsafe 应用 在对Unsafe的基础有了一定了解后,我们来看一下它的基本应用。由于篇幅有限,不能对所有方法进行介绍,如果大家有学习的需要,可以下载openJDK的源码进行学习。 1、内存操作 如果你是一个写过c或者c++的程序员,一定对内存操作不会陌生,而在java中是不允许直接对内存进行操作的,对象内存的分配和回收都是由jvm自己实现的。但是在Unsafe中,提供的下列接口可以直接进行内存操作: 使用下面的代码进行测试: 先看结果输出: 分析一下运行结果,首先使用allocateMemory方法申请4字节长度的内存空间,在循环中调用setMemory方法向每个字节写入内容为byte类型的1,当使用Unsafe调用getInt方法时,因为一个int型变量占4个字节,会一次性读取4个字节,组成一个int的值,对应的十进制结果为16843009,可以通过图示理解这个过程: 在代码中调用reallocateMemory方法重新分配了一块8字节长度的内存空间,通过比较addr和addr3可以看到和之前申请的内存地址是不同的。在代码中的第二个for循环里,调用copyMemory方法进行了两次内存的拷贝,每次拷贝内存地址addr开始的4个字节,分别拷贝到以addr3和addr3+4开始的内存空间上: 拷贝完成后,使用getLong方法一次性读取8个字节,得到long类型的值为72340172838076673。 需要注意,通过这种方式分配的内存属于堆外内存,是无法进行垃圾回收的,需要我们把这些内存当做一种资源去手动调用freeMemory方法进行释放,否则会产生内存泄漏。通用的操作内存方式是在try中执行对内存的操作,最终在finally块中进行内存的释放。 2、内存屏障 在介绍内存屏障前,需要知道编译器和CPU会在保证程序输出结果一致的情况下,会对代码进行重排序,从指令优化角度提升性能。而指令重排序可能会带来一个不好的结果,导致CPU的高速缓存和内存中数据的不一致,而内存屏障(Memory Barrier)就是通过组织屏障两边的指令重排序从而避免编译器和硬件的不正确优化情况。 在硬件层面上,内存屏障是CPU为了防止代码进行重排序而提供的指令,不同的硬件平台上实现内存屏障的方法可能并不相同。在java8中,引入了3个内存屏障的函数,它屏蔽了操作系统底层的差异,允许在代码中定义、并统一由jvm来生成内存屏障指令,来实现内存屏障的功能。Unsafe中提供了下面三个内存屏障相关方法: 内存屏障可以看做对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。以loadFence方法为例,它会禁止读操作重排序,保证在这个屏障之前的所有读操作都已经完成,并且将缓存数据设为无效,重新从主存中进行加载。 看到这估计很多小伙伴们会想到volatile关键字了,如果在字段上添加了volatile关键字,就能够实现字段在多线程下的可见性。基于读内存屏障,我们也能实现相同的功能。下面定义一个线程方法,在线程中去修改flag标志位,注意这里的flag是没有被volatile修饰的: 在主线程的while循环中,加入内存屏障,测试是否能够感知到flag的修改变化: 运行结果: 而如果删掉上面代码中的loadFence方法,那么主线程将无法感知到flag发生的变化,会一直在while中循环。可以用图来表示上面的过程: 了解java内存模型(JMM)的小伙伴们应该清楚,运行中的线程不是直接读取主内存中的变量的,只能操作自己工作内存中的变量,然后同步到主内存中,并且线程的工作内存是不能共享的。上面的图中的流程就是子线程借助于主内存,将修改后的结果同步给了主线程,进而修改主线程中的工作空间,跳出循环。 […]

IDEA Debug的实现原理(转)

https://mp.weixin.qq.com/s/BRPWRNst4HHqXPb3ywEZ2g # 对 Debug 的好奇 初学 Java 时,我对 IDEA 的 Debug 非常好奇,不止是它能查看断点的上下文环境,更神奇的是我可以在断点处使用它的 Evaluate 功能直接执行某些命令,进行一些计算或改变当前变量。 刚开始语法不熟经常写错代码,重新打包部署一次代码耗时很长,我就直接面向 Debug 开发。在要编写的方法开始处打一个断点,在 Evaluate 框内一次次地执行方法函数不停地调整代码,没问题后再将代码复制出来放到 IDEA 里,再进行下一个方法的编写,这样就跟写 PHP 类似的解释性语言一样,写完即执行,非常方便。 但 Java 是静态语言,运行之前是要先进行编译的,难道我写的这些代码是被实时编译又”注入”到我正在 Debug 的服务里了吗? 随着对 Java 的愈加熟悉,我也了解了反射、字节码等技术,直到前些天的周会分享,有位同事分享了 Btrace 的使用和实现,提到了 Java 的 ASM 框架和 JVM TI 接口。Btrace 修改代码能力的实现与 Debug 的 Evaluate 有很多相似之处,这大大吸引了我。 分享就像一个引子,从中学到的东西只是皮毛,要了解它还是要自己研究。于是自己查看资料并写代码学习了下其具体实现。 # ASM 实现 Evaluate 要解决的第一个问题就是怎么改变原有代码的行为,它的实现在 Java 里被称为动态字节码技术。 动态生成字节码 我们知道,我们编写的 […]

Java泛型可行与不可行

泛型基础 理解 一般情况,一个类的属性,或者一个方法的参数/返回值都需要在编写代码时声明基本类型或者自定义类型,但有时候无法在编写代码时使用现有的类来表达参数类型或者返回值类型,这时候就需有一种方式可以表达下面的意思:这里需要一个类,它满足这些要求就可以了,具体是什么类可以在使用这个类或方法时指定。Java中这种方式就是泛型。但是java泛型在使用上有很多限制,使用时要注意,同时注意泛型主义上的理解,Java中泛型的声明使用更多 作用 一定程序上继承与接口就可以完成上面的功能,但泛型有很多额外的作用 泛型可以更安全 使用泛型就是告诉编译器想使用什么类型,在使用泛型时编译器会对代码进行类型检查,让错误暴露在编译期,而不是运行期,更安全 可以快速创建复杂的类型 因为在编写时没有指定具体类型,所以在使用时就可以更随意的指定类型,这个功能可以完成类似js中对象的功能,对象的属性规定好,具体是什么类型你随便,但是没能像js那样随意添加属性 可以自动完成类型的转换 在泛型出现之前,如果一个方法不能确定方法的返回值类型,或者根据入参可以确定多种类型返回值类型,那么这个方法就只能返回Object ,有了泛型之后,在方法返回正确的值后,会自动转为具体的类型,而这在代码上没有额外的代码,而且这种转换很安全 上面例子编译之后再反编译回来 make 方法是这样的 再看一个调用时的代码 反编译之后 可以看到自动对参数进行了转型,所以编译器不会产生转型警告 还有一些更高级的用法,比如 泛型自限定 困难之处 书写泛型代码的主要困难是因为泛型在运行时被擦除,所以在运行期没有泛型类的具体信息,这意味着泛型参数看上去就借一个Object类,什么都干不了,需要注意以下方法 同样的类型,不同的泛型参数在编译期代表着不同类型,在运行期就没有差别了 不能使用 new 来创建泛型类型的具体对象,最好的方案是使用 Class.newInstance()或者使用工场模式 不能使用 instanceof 操作符了,但可以用 Class.isInstance(Object)方法 不能new 一个泛型数组,而且要产生泛型数组非常麻烦,可以使用Array.newInstance(Class,int) 但是这样也会有警告,需要压制 除非设定边界,否则不能调用任何自定义的方法 基本类型不能作为泛型参数,但是其包装类型可以,并可以自动包装 一个类不能实现同一个泛型接口的两种变体,但去掉泛型实现可以; 不能通过不同的泛型参数进行方法重载,但是可以使用 <R extends List<?>> 给泛型参数添加边界重载方法 泛型边界 可以使用 extends 限定泛型类型的边界,可以是多个(&连接),类写在前面,限定边界之后在泛型方法或者类的内部就可以使用边界类上的方法了 通配符 通配符在泛型中的应用是为了解决下面的问题:有一个容器的泛型是基类的变量,想要将一个泛型是子类的容器赋值给这个变量,编译器是不允许的;因为运行时会将泛型擦除,一旦将一个泛型是子类的容器赋值给泛型是基类的容器变量,在运行时就可以将一个这个基类的其他子类对象放入这个窗口,造成在取出对象时的类型不安全,所以编译期不允许这样赋值; 容器的这一特点与数组不同,子类数组对象可以赋值给基类数组变量(类似向上转型),但是在运行期jvm 可以知道数组元素中的对象类型是哪个具体子类,所以如果将数组中元素赋值时,如果不是原数组中的类型,会报错(ArrayStoreException) 为了保证类型安全,又可以将子类泛型容器赋值给基类泛型变量,可以使用通配符(单一边界,extends 后面只能有一个类型) 通配符的困难之处 当一个类在声明时使用了 这种泛型,而这个类的写法如同下面这样 […]

lWoHvYe 无悔,专一