关于 Java 9 的新特性从某本书的最后一个说起:平台日志 API。个人没感觉这个有什么实质的用途,所谓的平台日志是指 JDK 自身代码,或者是 JVM 组件中的日志输出,而在自己应用程序代码中却不会去用这个平台日志 API。这个所谓的 Platform Logging API 名称的意义也就是在这里,平台用的,在诊断时用来观察 JDK 类或 JVM 中的日志输出,比如应该可以截获到 JVM 本地代码实现中的日志输出。对我们在项目中如何处理日志并不会有什么影响,该怎么还是怎么,不过了解多一点东西应该不会浪费脑容量的。
新加的平台日志体现在 java.lang.System
中新加的几个方法和类
我们可以尝试着在代码使用一下它
1 2 |
System.Logger logger = System.getLogger(TestLogging.class.getName()); logger.log(System.Logger.Level.INFO, "Hello Java 9 Platform Logging API"); |
输出如下
May 26, 2018 10:56:51 AM cc.unmi.TestLogging main
INFO: Hello Java 9 Platform Logging API
新 API 有
java.lang.System.Logger 接口
java.lang.System.LoggerFinder 用来查找上面 Logger 的实现类
java.lang.System.getLogger(...) 方法,它们会通过 LoggerFinder 找到相应的 Logger 实现
默认的,System.getLogger(...) 会使用 JDK 的 java.util.logging
(如果该模块存在时,即 JUL,它默认只输入 INFO 及更高级别的日志) 作为它的日志实现,所以前面的日志输出其实就是 JUL 的输出。见包 sun.util.logging.internal
中的
1 2 3 4 5 6 7 8 |
public final class LoggingProviderImpl extends DefaultLoggerFinder { ........ static final class JULWrapper extends LoggerConfiguration implements System.Logger, PlatformLogger.Bridge, PlatformLogger.ConfigurableBridge { ........ } } |
System.LoggerFinder 和 System.Logger 默认就是这两个实现类。
如果 JUL 模块不存在,System.Logger
将把 INFO 及更高级别的日志输出到 System.err
标准错误输出。
由此我们看到 System.Logger
也就是作为 JUL 的一个门面,老实说它提供的日志输出方法还不如 JUL 的 java.util.logging.Logger
友好。System.Logger
的每个日志方法都要带上日志级别
JUL
的 Logger 除了提供带日志级别为参数的通用 log(Level, ...)
方法外,还为每一个日志级别提供了像下面那些便利方法(以 warning 为例)
warning(String msg)
warning(Supplier<String> msgSuppiler)
既然 System.Logger
是一个门面,那么它通过自定的 System.Logger
和 System.LoggerFinder
也能让它的日志桥接到其他日志框架上去,如 Apache Commong Logging(JCL - Jakarta Commons Logging), SLF4J, 或具体的日志实现 Log4j 或 Logback 去。
Java 9 新加这么一个日志框架门面显得有些累赘了,JUL
我们就不会去用它,有了更好的 SLF4J 更不会去用 System.Logger
。不过它也说了 JDK 自己的代码会用 System.Logger
, 那么我们有可能要做的就是如何把 JDK 中的 System.Logger
日志输出导向到我们熟悉的日志框架中来。
日志框架越发复杂了,应该说来,目前更广为人知的用法还是 SLF4J + Log4J 或 SLF4J + Logback, 我更推崇后者。至于 JCL + Log4J 的用法越发稀少,而 Logback 比之 JCL 是个新鲜事物,它基本就是为 SLF4J 设计的。当前业界为了各个组件中使用的不同日志框架而统一日志输出目标,产生了诸如以下适配器
jcl-over-slf4j, slf4j-jcl, log4j-over-slf4j, slf4j-log4j12, jul-to-slf4j
倒是没有找到 slf4j-over-jcl 这样的东西,因此 SLF4J 作为日志框架的门面还是当前趋势。简单项目中直接用 Log4J 或 Logback 也是可以的,Logback 自身就依赖了 SLF4J。
因为 JDK/JVM 用 System.Logger
来输出日志,它默认使用 JUL(当该模块存在时) 实现,所以我们只要用 jul-to-slf4j bridge
就能按需把 JDK/JVM 的日志输出到我们想要的地方去。
附:前面说了 JUL 默认只显示 INFO 或更高级别的日志,也就是 CONFIG, FINE, FINER, FINEST 的日志不会显示,那么要降低日志输出级别该怎么做呢?下面代码将开启所有日志输出
1 2 3 4 5 |
Logger rootLogger = LogManager.getLogManager().getLogger(""); rootLogger.setLevel(Level.ALL); for (Handler hander : rootLogger.getHandlers()) { hander.setLevel(Level.ALL); } |
JUL 的日志级别还与 System.Logger
的日志级别还有差异(它们原本就不是一家人)
JUL 的日志级别:OFF, SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST, ALL
System.Logger 的日志级别分别是:OFF, ERROR, WARNING, INFO, DEBUG, TRACE, ALL ---- 这个更符合主流意识形态
当 System.Logger
默认采用 JUL 来输出日志时,它们的日志级别映射关系可查看 sun.util.logging.PlatformLogger
1 2 3 4 5 6 7 8 9 |
ALL(System.Logger.Level.ALL), FINEST(System.Logger.Level.TRACE), FINER(System.Logger.Level.TRACE), FINE(System.Logger.Level.DEBUG), CONFIG(System.Logger.Level.DEBUG), INFO(System.Logger.Level.INFO), WARNING(System.Logger.Level.WARNING), SEVERE(System.Logger.Level.ERROR), OFF(System.Logger.Level.OFF); |
Java 9 的 Platform Logging API
基本上就说这么多了,只是让大家了解一下 Java 9 中有这么一个东西,它能做什么 事。并没有像某书中那样纠缠于如何自定义 System.Logger
和 System.LoggerFinder
,以及应用 SPI
来把平台日志输出到 Log4J
去,因为那么做的现实意义不大,我们项目中只需要专注于 SLF4J + Logback
的应用。
一个 Java 平台类使用 System.Logger 的例子
java.util.Currency
类中就使用到了 System.Logger
,
1 2 3 4 5 6 7 8 9 10 |
private static void info(String message, Throwable t) { PlatformLogger logger = PlatformLogger.getLogger("java.util.Currency"); if (logger.isLoggable(PlatformLogger.Level.INFO)) { if (t != null) { logger.info(message, t); } else { logger.info(message); } } } |
当该类在初始化时如果不能解析文件 JAVA_HOME/lib/currency.properties
文件时就报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
static { ...... // look for the properties file for overrides String propsFile = System.getProperty("java.util.currency.data"); if (propsFile == null) { propsFile = System.getProperty("java.home") + File.separator + "lib" + File.separator + "currency.properties"; } try { File propFile = new File(propsFile); if (propFile.exists()) { ......//parse currency.properties file } } catch (IOException e) { info("currency.properties is ignored because of an IOException", e); } ...... } |
所以假如我们创建文件 JAVA_HOME/lib/currency.properties
, 并在其中放入不合法的内容,如
ABadCurrencyFile
然后执行代码
1 |
Currency.getInstance("USD"); |
这会去触发 Currency
的 static 块,文件无法解析,控制台可以看到输出
May 26, 2018 1:19:15 PM java.util.Currency info
INFO: currency.properties entry for ABADCURENCYFILE is ignored because of the invalid country code.
这就是平台日志的用法,还有新增的虚拟机参数 -Xlog
也是在控制平台的日志输出。
链接:
本文链接 https://yanbin.blog/java-9-platform-logging-api/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
我现在有一个问题,一直想搞清楚
我在method里面创建一个对象,这个对象是在堆里面存放到还是在栈里面存放的呢?method执行完,这个对象会被回收吗?
如果我想看对象创建后在堆区还是在栈区,有没有什么工具可以跟踪到,或者说怎么去证明这个对象在堆中呢?
看到您这个文章,利用这个新的logger接口,有没有可能去实现这样一个证明呢?
创建的对象都是放在堆中,方法里声明的变量是局部变量,方法桢一旦弹出,局部变量引用的堆中的对象便可被回收。自己的代码中不用去直接使用新的 Logger 接口。
最近听到一个说法,对象创建并不一定都是存放在堆中,有可能会创建在栈里面。我今天发现要证明对象存放在堆中还是可以借助jstat命令来实现,但是要证明不在堆中,没想到方法,有什么命令可以来监测栈吗?
对象都是放在堆中,只有对象引用,或原始类型才放到栈里面。
可以设置栈上分配,现在1.8默认就是开启的,有些对象通过逃逸分析,别认定可以逃逸的情况下,jvm默认会分配到栈上的
这个问题有意思,找到一个参考链接:http://www.vogella.com/tutorials/JavaPerformance/article.html#escape-analysis 2.5. Escape analysis As stated earlier Java objects are created and stored in the heap. The programming language does not offer the possibility to let the programmer decide if an object should be generated in the stack. But in certain cases it would be desirable to allocate an object on the stack, as the memory allocation on the stack is cheaper than the memory allocation in the heap, deallocation on the stack is free and the stack is efficiently managed by the runtime. The JVM uses therefore internally escape analysis to check if an object is used only with… Read more »
这块,我倒是还没有看到呢,分配在栈上的对象进行传递。
这里说的对象传递是传递给另外方法吗?相当于把对象传出去?还有拷贝是拷贝到其他到栈还是到堆里面呢?
其实控制逃逸分析倒是可以控制,主要是写代码到时候需要刻意去按照逃逸到方式编写,应该是可以实现的。
栈上对象传递给另一个方法,应该也像 C++的栈上对象传递一样。